updated sync

This commit is contained in:
Geoffrey Goh
2015-11-17 18:24:56 -08:00
parent ff891dba76
commit a97407508b
20 changed files with 266 additions and 156 deletions

View File

@@ -49,7 +49,7 @@ failCallback:(void (^)(NSError *err))failCallback;
@interface CodePushPackage : NSObject
+ (void)applyPackage:(NSDictionary *)updatePackage
+ (void)installPackage:(NSDictionary *)updatePackage
error:(NSError **)error;
+ (NSDictionary *)getCurrentPackage:(NSError **)error;
@@ -72,8 +72,8 @@ failCallback:(void (^)(NSError *err))failCallback;
@end
typedef NS_ENUM(NSInteger, CodePushRestartMode) {
CodePushRestartModeNone,
CodePushRestartModeImmediate,
CodePushRestartModeOnNextResume
typedef NS_ENUM(NSInteger, CodePushInstallMode) {
CodePushInstallModeImmediate,
CodePushInstallModeOnNextRestart,
CodePushInstallModeOnNextResume
};

View File

@@ -60,7 +60,7 @@ function getCurrentPackage() {
return NativeCodePush.isFailedUpdate(currentPackage.packageHash);
})
.then((failedUpdate) => {
localPackage.failedApply = failedUpdate;
localPackage.failedInstall = failedUpdate;
return NativeCodePush.isFirstRun(localPackage.packageHash);
})
.then((isFirstRun) => {
@@ -98,7 +98,7 @@ function checkForUpdate() {
}
// Ignore updates that require a newer app version,
// since the end-user couldn't reliably apply it
// since the end-user couldn't reliably install it
if (!update || update.updateAppVersion) {
return resolve(null);
}
@@ -107,7 +107,7 @@ function checkForUpdate() {
NativeCodePush.isFailedUpdate(update.packageHash)
.then((isFailedHash) => {
update.failedApply = isFailedHash;
update.failedInstall = isFailedHash;
resolve(update);
})
.catch(reject)
@@ -126,72 +126,106 @@ function checkForUpdate() {
* releases, and displaying a standard confirmation UI to the end-user
* when an update is available.
*/
function sync(options = {}) {
function sync(options = {}, onSyncStatusChange, onDownloadProgress) {
var syncOptions = {
descriptionPrefix: " Description: ",
appendReleaseDescription: false,
ignoreFailedUpdates: true,
mandatoryContinueButtonLabel: "Continue",
mandatoryUpdateMessage: "An update is available that must be installed.",
optionalIgnoreButtonLabel: "Ignore",
optionalInstallButtonLabel: "Install",
optionalUpdateMessage: "An update is available. Would you like to install it?",
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
rollbackTimeout: 0,
updateTitle: "Update available",
updateDialog: null,
...options
};
onSyncStatusChange = typeof onSyncStatusChange == "function"
? onSyncStatusChange
: function(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
console.log("Checking for update.");
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("Downloading package.");
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
console.log("Awaiting user action.");
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
console.log("Installing update.");
break;
case CodePush.SyncStatus.IDLE:
console.log("Sync is idle.");
break;
}
};
onDownloadProgress = typeof onDownloadProgress == "function"
? onDownloadProgress
: function(downloadProgress) {
console.log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`);
};
return new Promise((resolve, reject) => {
onSyncStatusChange(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
checkForUpdate()
.then((remotePackage) => {
if (!remotePackage || (remotePackage.failedApply && syncOptions.ignoreFailedUpdates)) {
resolve(CodePush.SyncStatus.UP_TO_DATE);
var doDownloadAndInstall = () => {
onSyncStatusChange(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
remotePackage.download(onDownloadProgress)
.then((localPackage) => {
onSyncStatusChange(CodePush.SyncStatus.INSTALLING_UPDATE);
return localPackage.install(syncOptions.rollbackTimeout, syncOptions.installMode)
})
.then(() => {
onSyncStatusChange(CodePush.SyncStatus.IDLE);
resolve(CodePush.SyncResult.UPDATE_INSTALLED)
})
.catch(reject)
.done();
}
else {
if (!remotePackage || (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates)) {
onSyncStatusChange(CodePush.SyncStatus.IDLE);
resolve(CodePush.SyncResult.UP_TO_DATE);
}
else if (syncOptions.updateNotification) {
syncOptions.updateNotification = Object.assign(CodePush.DEFAULT_UPDATE_DIALOG, syncOptions.updateNotification);
var message = null;
var dialogButtons = [
{
text: null,
onPress: () => {
remotePackage.download()
.then((localPackage) => {
resolve(CodePush.SyncStatus.UPDATE_APPLIED)
return localPackage.apply(syncOptions.rollbackTimeout);
})
.catch(reject)
.done();
doDownloadAndInstall();
}
}
];
if (remotePackage.isMandatory) {
message = syncOptions.mandatoryUpdateMessage;
message = syncOptions.updateNotification.mandatoryUpdateMessage;
dialogButtons[0].text = syncOptions.mandatoryContinueButtonLabel;
} else {
message = syncOptions.optionalUpdateMessage;
dialogButtons[0].text = syncOptions.optionalInstallButtonLabel;
message = syncOptions.updateNotification.optionalUpdateMessage;
dialogButtons[0].text = syncOptions.updateNotification.optionalInstallButtonLabel;
// Since this is an optional update, add another button
// to allow the end-user to ignore it
dialogButtons.push({
text: syncOptions.optionalIgnoreButtonLabel,
onPress: () => resolve(CodePush.SyncStatus.UPDATE_IGNORED)
text: syncOptions.updateNotification.optionalIgnoreButtonLabel,
onPress: () => resolve(CodePush.SyncResult.UPDATE_IGNORED)
});
}
// If the update has a description, and the developer
// explicitly chose to display it, then set that as the message
if (syncOptions.appendReleaseDescription && remotePackage.description) {
message += `${syncOptions.descriptionPrefix} ${remotePackage.description}`;
if (syncOptions.updateNotification.appendReleaseDescription && remotePackage.description) {
message += `${syncOptions.updateNotification.descriptionPrefix} ${remotePackage.description}`;
}
onSyncStatusChange(CodePush.SyncStatus.AWAITING_USER_ACTION);
AlertIOS.alert(syncOptions.updateTitle, message, dialogButtons);
} else {
doDownloadAndInstall();
}
})
.catch(reject)
@@ -206,15 +240,32 @@ var CodePush = {
notifyApplicationReady: NativeCodePush.notifyApplicationReady,
setUpTestDependencies: setUpTestDependencies,
sync: sync,
RestartMode: {
NONE: NativeCodePush.codePushRestartModeNone, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
IMMEDIATE: NativeCodePush.codePushRestartModeImmediate, // Restart the app immediately
ON_NEXT_RESUME: NativeCodePush.codePushRestartModeOnNextResume // Restart the app the next time it is resumed from the background
InstallMode: {
IMMEDIATE: NativeCodePush.codePushInstallModeImmediate, // Restart the app immediately
ON_NEXT_RESTART: NativeCodePush.codePushInstallModeOnNextRestart, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
ON_NEXT_RESUME: NativeCodePush.codePushInstallModeOnNextResume // Restart the app the next time it is resumed from the background
},
SyncStatus: {
SyncResult: {
UP_TO_DATE: 0, // The running app is up-to-date
UPDATE_IGNORED: 1, // The app had an optional update and the end-user chose to ignore it
UPDATE_APPLIED: 2 // The app had an optional/mandatory update that was successfully downloaded and is about to be applied
UPDATE_INSTALLED: 2 // The app had an optional/mandatory update that was successfully downloaded and is about to be installed.
},
SyncStatus: {
CHECKING_FOR_UPDATE: 1,
AWAITING_USER_ACTION: 2,
DOWNLOADING_PACKAGE: 3,
INSTALLING_UPDATE: 4,
IDLE: 5
},
DEFAULT_UPDATE_DIALOG: {
appendReleaseDescription: false,
descriptionPrefix: " Description: ",
mandatoryContinueButtonLabel: "Continue",
mandatoryUpdateMessage: "An update is available that must be installed.",
optionalIgnoreButtonLabel: "Ignore",
optionalInstallButtonLabel: "Install",
optionalUpdateMessage: "An update is available. Would you like to install it?",
updateTitle: "Update available",
}
};

View File

@@ -102,11 +102,11 @@ NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
- (NSDictionary *)constantsToExport
{
// Export the values of the CodePushRestartMode enum
// Export the values of the CodePushInstallMode enum
// so that the script-side can easily stay in sync
return @{ @"codePushRestartModeNone": @(CodePushRestartModeNone),
@"codePushRestartModeImmediate": @(CodePushRestartModeImmediate),
@"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume)
return @{ @"codePushInstallModeOnNextRestart": @(CodePushInstallModeOnNextRestart),
@"codePushInstallModeImmediate": @(CodePushInstallModeImmediate),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume)
};
};
@@ -124,7 +124,7 @@ NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
if (self) {
// Do an async check to see whether
// we need to start the rollback timer
// due to a pending update being applied at start
// due to a pending update being installed at start
[self checkForPendingUpdate:NO];
// Register for app resume notifications so that we
@@ -204,7 +204,7 @@ NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
rollbackTimeout:(int)rollbackTimeout
{
// Since we're not restarting, we need to store the fact that the update
// was applied, but hasn't yet become "active".
// was installed, but hasn't yet become "active".
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys:
packageHash,PendingUpdateHashKey,
@@ -225,26 +225,28 @@ NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
// JavaScript-exported module methods
RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage
RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
rollbackTimeout:(int)rollbackTimeout
restartMode:(CodePushRestartMode)restartMode
installMode:(CodePushInstallMode)installMode
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error;
[CodePushPackage applyPackage:updatePackage
[CodePushPackage installPackage:updatePackage
error:&error];
if (error) {
reject(error);
} else {
if (restartMode == CodePushRestartModeImmediate) {
if (installMode == CodePushInstallModeImmediate) {
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES];
} else {
_resumablePendingUpdateAvailable = (restartMode == CodePushRestartModeOnNextResume);
_resumablePendingUpdateAvailable = (installMode == CodePushInstallModeOnNextResume);
[self savePendingUpdate:updatePackage[@"packageHash"]
rollbackTimeout:rollbackTimeout];
// Signal to JS that the update has been applied.
resolve(nil);
}
}
});

View File

@@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */; };
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; };
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; };
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
@@ -30,7 +30,7 @@
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushRestartMode.m"; sourceTree = "<group>"; };
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushInstallMode.m"; sourceTree = "<group>"; };
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
@@ -58,7 +58,7 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */,
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */,
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
@@ -124,7 +124,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */,
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */,
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,

View File

@@ -208,7 +208,7 @@ NSString * const UpdateBundleFileName = @"app.jsbundle";
[downloadHandler download:updatePackage[@"downloadUrl"]];
}
+ (void)applyPackage:(NSDictionary *)updatePackage
+ (void)installPackage:(NSDictionary *)updatePackage
error:(NSError **)error
{
NSString *packageHash = updatePackage[@"packageHash" ];

View File

@@ -24,7 +24,7 @@
544161591B8BCA81000D9E25 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */; };
5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */; };
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */; };
54D774BA1B87DAF800F2ABF8 /* InstallUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */; };
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */; settings = {ASSET_TAGS = (); }; };
81551E1B1B3B428000F5B9F1 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; };
/* End PBXBuildFile section */
@@ -151,7 +151,7 @@
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; };
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryUpdateTests.m; sourceTree = "<group>"; };
5451ACE61B86E34300E2A7DF /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = "node_modules/react-native/Libraries/RCTTest/RCTTest.xcodeproj"; sourceTree = "<group>"; };
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplyUpdateTests.m; sourceTree = "<group>"; };
54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstallUpdateTests.m; sourceTree = "<group>"; };
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../CodePush.xcodeproj; sourceTree = "<group>"; };
@@ -242,7 +242,7 @@
children = (
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */,
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */,
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */,
54D774B91B87DAF800F2ABF8 /* InstallUpdateTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = CodePushDemoAppTests;
@@ -612,7 +612,7 @@
files = (
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */,
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */,
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */,
54D774BA1B87DAF800F2ABF8 /* InstallUpdateTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -15,11 +15,11 @@
#define TIMEOUT_SECONDS 60
#define TEXT_TO_LOOK_FOR @"If you see this, you have successfully installed an update!"
@interface ApplyUpdateTests : XCTestCase
@interface InstallUpdateTests : XCTestCase
@end
@implementation ApplyUpdateTests
@implementation InstallUpdateTests
{
RCTTestRunner *_runner;
NSString* app;
@@ -27,7 +27,7 @@
- (void)setUp
{
app = @"CodePushDemoAppTests/ApplyUpdateTests/ApplyUpdateTestApp.ios";
app = @"CodePushDemoAppTests/InstallUpdateTests/InstallUpdateTestApp.ios";
#if __LP64__
RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
#endif
@@ -51,7 +51,7 @@
}
#pragma mark Logic Tests
- (void)testDownloadAndApplyUpdate
- (void)testDownloadAndInstallUpdate
{
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
@@ -68,7 +68,7 @@
moduleProvider:nil
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"DownloadAndApplyUpdateTest"
moduleName:@"DownloadAndInstallUpdateTest"
initialProperties:nil];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];

View File

@@ -9,7 +9,7 @@ var {
View,
} = React;
var DownloadAndApplyUpdateTest = React.createClass({
var DownloadAndInstallUpdateTest = React.createClass({
propTypes: {
shouldThrow: React.PropTypes.bool,
waitOneFrame: React.PropTypes.bool,
@@ -39,7 +39,7 @@ var DownloadAndApplyUpdateTest = React.createClass({
runTest() {
var update = require("./TestPackage");
NativeBridge.downloadUpdate(update).done((downloadedPackage) => {
NativeBridge.applyUpdate(downloadedPackage, /*rollbackTimeout*/ 1000, /*restartImmediately*/ true);
NativeBridge.installUpdate(downloadedPackage, /*rollbackTimeout*/ 1000, /*restartImmediately*/ true);
});
},
@@ -55,6 +55,6 @@ var DownloadAndApplyUpdateTest = React.createClass({
}
});
DownloadAndApplyUpdateTest.displayName = 'DownloadAndApplyUpdateTest';
DownloadAndInstallUpdateTest.displayName = 'DownloadAndInstallUpdateTest';
module.exports = DownloadAndApplyUpdateTest;
module.exports = DownloadAndInstallUpdateTest;

View File

@@ -15,14 +15,14 @@ var {
} = React;
var TESTS = [
require('./DownloadAndApplyUpdateTest')
require('./DownloadAndInstallUpdateTest')
];
TESTS.forEach(
(test) => AppRegistry.registerComponent(test.displayName, () => test)
);
var ApplyUpdateTestApp = React.createClass({
var InstallUpdateTestApp = React.createClass({
getInitialState: function() {
return {
test: null,
@@ -79,4 +79,4 @@ var styles = StyleSheet.create({
}
});
AppRegistry.registerComponent('ApplyUpdateTestApp', () => ApplyUpdateTestApp);
AppRegistry.registerComponent('InstallUpdateTestApp', () => InstallUpdateTestApp);

View File

@@ -1,5 +1,5 @@
var testPackage = {
downloadUrl: "http://localhost:8081/CodePushDemoAppTests/ApplyUpdateTests/CodePushDemoApp.ios.includeRequire.runModule.bundle?dev=true",
downloadUrl: "http://localhost:8081/CodePushDemoAppTests/InstallUpdateTests/CodePushDemoApp.ios.includeRequire.runModule.bundle?dev=true",
description: "Angry flappy birds",
appVersion: "1.5.0",
label: "2.4.0",

View File

@@ -8,5 +8,8 @@ Test Cases
* testNewUpdate - Checks that when the remote server has a new package with a different package hash and same version as the current package, CodePushSdk.queryUpdate returns that new package without throwing an error.
* testSamePackage - Checks that when the remote server has a package that is identical to the current package, CodePushSdk.queryUpdate does not return a new package nor throw an error.
* ApplyUpdateTests - Tests the functionality of installing new app updates downloaded from the server via the SDK
* testDownloadAndApplyUpdate - Queries for a new update, downloads it and then verifies that from the UI that the new update has been installed.
* InstallUpdateTests - Tests the functionality of installing new app updates downloaded from the server via the SDK
* testDownloadAndInstallUpdate - Queries for a new update, downloads it and then verifies that from the UI that the new update has been installed.
* DownloadProgressTests - Tests the functionality of downloading app updates and tracking the download progress via the SDK
* testDownloadProgress - Downloads three files of different sizes and verifies that the reported number of bytes tally.

View File

@@ -20,49 +20,98 @@ var CodePush = require('react-native-code-push');
var CodePushDemoApp = React.createClass({
componentDidMount: function() {
this.checkUpdate();
},
checkUpdate: function() {
CodePush.checkForUpdate().done((update) => {
this.setState({ update: update });
sync: function() {
var self = this;
CodePush.sync(
{
updateNotification: true,
installMode: CodePush.InstallMode.ON_NEXT_RESUME
},
function(syncStatus) {
switch(syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
self.setState({
syncMessage: "Checking for update."
});
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
self.setState({
syncMessage: "Downloading package."
});
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
self.setState({
syncMessage: "Awaiting user action."
});
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
self.setState({
syncMessage: "Installing update."
});
break;
case CodePush.SyncStatus.IDLE:
self.setState({
syncMessage: "Update installed and will be run when the app next resumes.",
progress: false
});
break;
}
},
function(progress) {
self.setState({
progress: progress
});
}
).then(function(syncResult) {
switch(syncResult) {
case CodePush.SyncResult.UP_TO_DATE:
self.setState({
syncMessage: "App up to date."
});
break;
case CodePush.SyncResult.UPDATE_IGNORED:
self.setState({
syncMessage: "Update cancelled by user."
});
break;
}
});
CodePush.notifyApplicationReady().done();
},
getInitialState: function() {
return { update: false };
return { };
},
handlePress: function() {
this.state.update.download((progress) => {
this.setState({
progress:progress
});
}).done((localPackage) => {
localPackage.apply().done();
});
},
render: function() {
var updateView;
var syncView;
var syncButton;
var progressView;
if (this.state.syncMessage) {
syncView = (
<Text style={styles.messages}>{this.state.syncMessage}</Text>
);
} else {
syncButton = (
<Button style={{color: 'green'}} onPress={this.sync}>
Start Sync!
</Button>
);
}
if (this.state.progress) {
updateView = (
<Text>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
progressView = (
<Text style={styles.messages}>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
);
} else if (this.state.update) {
updateView = (
<View>
<Text>Update Available: {'\n'} {this.state.update.scriptVersion} - {this.state.update.description}</Text>
<Button style={{color: 'green'}} onPress={this.handlePress}>
Update
</Button>
</View>
);
};
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to Code Push!
Welcome to CodePush!
</Text>
{updateView}
{syncButton}
{syncView}
{progressView}
</View>
);
}
@@ -71,7 +120,6 @@ var CodePushDemoApp = React.createClass({
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
@@ -79,11 +127,10 @@ var styles = StyleSheet.create({
fontSize: 20,
textAlign: 'center',
margin: 10,
marginTop: 50
},
instructions: {
messages: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

View File

@@ -0,0 +1,14 @@
#import "CodePush.h"
#import "RCTConvert.h"
// Extending the RCTConvert class allows the React Native
// bridge to handle args of type "CodePushInstallMode"
@implementation RCTConvert (CodePushInstallMode)
RCT_ENUM_CONVERTER(CodePushInstallMode, (@{ @"codePushInstallModeImmediate": @(CodePushInstallModeImmediate),
@"codePushInstallModeOnNextRestart": @(CodePushInstallModeOnNextRestart),
@"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume) }),
CodePushInstallModeImmediate, // Default enum value
integerValue)
@end

View File

@@ -1,14 +0,0 @@
#import "CodePush.h"
#import "RCTConvert.h"
// Extending the RCTConvert class allows the React Native
// bridge to handle args of type "CodePushRestartMode"
@implementation RCTConvert (CodePushRestartMode)
RCT_ENUM_CONVERTER(CodePushRestartMode, (@{ @"codePushRestartModeNone": @(CodePushRestartModeNone),
@"codePushRestartModeImmediate": @(CodePushRestartModeImmediate),
@"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) }),
CodePushRestartModeImmediate, // Default enum value
integerValue)
@end

View File

@@ -2,9 +2,9 @@
This plugin provides client-side integration for the [CodePush service](https://microsoft.github.io/code-push), allowing you to easily add a dynamic code update experience to your React Native apps.
The CodePush React Native API provides two primary mechanisms for discovering updates and dynamically applying them within your apps:
The CodePush React Native API provides two primary mechanisms for discovering updates and dynamically installing them within your apps:
1. [**Sync mode**](#codepushsync), which allows you to call a single method--presumably as part of mounting your app's root component or in response to a button click--that will automatically check for an update, download and apply it, while respecting the policies and metadata associated with each release (e.g. if the release is mandatory then it doesn't give the end-user the option to ignore it)
1. [**Sync mode**](#codepushsync), which allows you to call a single method--presumably as part of mounting your app's root component or in response to a button click--that will automatically check for an update, download and install it, while respecting the policies and metadata associated with each release (e.g. if the release is mandatory then it doesn't give the end-user the option to ignore it)
2. [**Advanced mode**](#codepushcheckforupdate), which provides a handful of "low-level" methods which give you complete control over the update experience, at the cost of added complexity.
When getting started using CodePush, we would recommended using the sync mode until you discover that it doesn't suit your needs. That said, if you have a user scenario
@@ -73,7 +73,7 @@ Once your Xcode project has been setup to build/link the CodePush plugin, you ne
jsCodeLocation = [CodePush getBundleUrl];
```
This change configures your app to always load the most recent version of your app's JS bundle. On the initial launch, this will correspond to the file that was compiled with the app. However, after an update has been pushed via CodePush, this will return the location of the most recently applied update.
This change configures your app to always load the most recent version of your app's JS bundle. On the initial launch, this will correspond to the file that was compiled with the app. However, after an update has been pushed via CodePush, this will return the location of the most recently installed update.
To let the CodePush runtime know which deployment it should query for updates against, perform the following steps:
@@ -122,9 +122,9 @@ And that's it! For more information regarding the CodePush API, including the va
When you require the `react-native-code-push` module, that object provides the following methods directly on it:
* [checkForUpdate](#codepushcheckforupdate): Queries the CodePush service for an update against the configured deployment. This method returns a promise which resolves to a `RemotePackage` that can be subsequently downloaded.
* [getCurrentPackage](#codepushgetcurrentpackage): Gets information about the currently applied package (e.g. description, installation time)
* [notifyApplicationReady](#codepushnotifyapplicationready): Notifies the CodePush runtime that an applied update is considered successful. This is an optional API, but is useful when you want to expicitly enable "rollback protection" in the event that an exception occurs in any code that you've deployed to production
* [sync](#codepushsync): Allows checking for an update, downloading it and applying it, all with a single call. Unless you need custom UI and/or behavior, we recommend most developers to use this method when integrating CodePush into their apps
* [getCurrentPackage](#codepushgetcurrentpackage): Gets information about the currently installed package (e.g. description, installation time)
* [notifyApplicationReady](#codepushnotifyapplicationready): Notifies the CodePush runtime that an installed update is considered successful. This is an optional API, but is useful when you want to expicitly enable "rollback protection" in the event that an exception occurs in any code that you've deployed to production
* [sync](#codepushsync): Allows checking for an update, downloading it and installing it, all with a single call. Unless you need custom UI and/or behavior, we recommend most developers to use this method when integrating CodePush into their apps
#### codePush.checkForUpdate
@@ -158,7 +158,7 @@ codePush.checkForUpdate().then((update) => {
codePush.getCurrentPackage(): Promise<LocalPackage>;
```
Gets information about the currently applied package (e.g. description, installation time).
Gets information about the currently installed package (e.g. description, installation time).
This method returns a Promise that resolves with the `LocalPackage` instance that represents the running update. This API is only useful for advanced scenarios, and so many devs won't need to concern themselves with it.
@@ -168,7 +168,7 @@ This method returns a Promise that resolves with the `LocalPackage` instance tha
codePush.notifyApplicationReady(): Promise<void>;
```
Notifies the CodePush runtime that an update is considered successful, and therefore, a rollback isn't neccessary. Calling this function is required whenever the `rollbackTimeout` parameter is specified when calling either ```LocalPackage.apply``` or `sync`. If you specify a `rollbackTimeout`, and don't call `notifyApplicationReady`, the CodePush runtime will assume that the applied update has failed and roll back to the previous version.
Notifies the CodePush runtime that an update is considered successful, and therefore, a rollback isn't neccessary. Calling this function is required whenever the `rollbackTimeout` parameter is specified when calling either ```LocalPackage.install``` or `sync`. If you specify a `rollbackTimeout`, and don't call `notifyApplicationReady`, the CodePush runtime will assume that the installed update has failed and roll back to the previous version.
If the `rollbackTimeout` parameter was not specified, the CodePush runtime will not enforce any automatic rollback behavior, and therefore, calling this function is not required and will result in a no-op.
@@ -178,10 +178,10 @@ If the `rollbackTimeout` parameter was not specified, the CodePush runtime will
codePush.sync(options: Object): Promise<Number>;
```
Provides a simple option for checking for an update, displaying a notification to the user, downloading it and then applying it, all while also respecting the policy that your release was published with. This method effectively composes together the "advanced mode" APIs for you, so that you don't need to handle any of the following scenarios yourself:
Provides a simple option for checking for an update, displaying a notification to the user, downloading it and then installing it, all while also respecting the policy that your release was published with. This method effectively composes together the "advanced mode" APIs for you, so that you don't need to handle any of the following scenarios yourself:
1. Checking for an update and displaying a standard confirmation dialog asking if they would like to install it
2. Automatically ignoring updates which have previously failed to apply (due to automatic rollback), and therefore, likely don't make sense trying to apply again (let's blacklist them!)
2. Automatically ignoring updates which have previously failed to install (due to automatic rollback), and therefore, likely don't make sense trying to install again (let's blacklist them!)
3. Looking to see whether an available update is mandatory, and if so, don't give the end-user the choice to ignore it
4. Displaying the description of an update to the end-user as part of the install confirmation experience
@@ -197,14 +197,14 @@ The method accepts an options object that allows you to customize numerous aspec
* __optionalIgnoreButtonLabel__ (String) - The text to use for the button the end-user can press in order to ignore an optional update that is available. Defaults to `"Ignore"`.
* __optionalInstallButtonLabel__ (String) - The text to use for the button the end-user can press in order to install an optional update. Defaults to `"Install"`.
* __optionalUpdateMessage__ (String) - The text used as the body of an update notification, when the update is optional. Defaults to `"An update is available. Would you like to install it?"`.
* __rollbackTimeout__ (String) - The number of seconds that you want the runtime to wait after an update has been applied before considering it failed and rolling it back. Defaults to `0`, which disabled rollback protection.
* __rollbackTimeout__ (String) - The number of seconds that you want the runtime to wait after an update has been installed before considering it failed and rolling it back. Defaults to `0`, which disabled rollback protection.
* __updateTitle__ (String) - The text used as the header of an update notification that is displayed to the end-user. Defaults to `"Update available"`.
The method returns a `Promise` that is resolved to a `SyncStatus` integer code, which indicates why the `sync` call succeeded. This code can be one of the following values:
The method returns a `Promise` that is resolved to a `SyncResult` integer code, which indicates why the `sync` call succeeded. This code can be one of the following values:
* __CodePush.SyncStatus.UP_TO_DATE__ *(0)* - The app doesn't have an available update.
* __CodePush.SyncStatus.UPDATE_IGNORED__ *(1)* - The app has an optional update, that the user chose to ignore.
* __CodePush.SyncStatus.UPDATE_APPLIED__ *(2)* - The app had an optional or mandatory update that was successfully downloaded and is about to be applied. If your app needs to do any data persistence/migration before restarting, this is the time to do it.
* __CodePush.SyncResult.UP_TO_DATE__ *(0)* - The app doesn't have an available update.
* __CodePush.SyncResult.UPDATE_IGNORED__ *(1)* - The app has an optional update, that the user chose to ignore.
* __CodePush.SyncResult.UPDATE_INSTALLED__ *(2)* - The app had an optional or mandatory update that was successfully downloaded and is about to be installed. If your app needs to do any data persistence/migration before restarting, this is the time to do it.
If the update check and/or the subseqeuent download fails for any reason, the `Promise` object returned by `sync` will be rejected with the reason.
@@ -213,9 +213,9 @@ Example Usage:
```javascript
codePush.sync()
.then((status) => {
if (status == codePush.SyncStatus.UPDATE_APPLIED) {
if (status == codePush.SyncResult.UPDATE_INSTALLED) {
// Do any neccessary work here before the app
// is restarted in order to apply the update
// is restarted in order to install the update
}
})
.catch((reason) => {
@@ -229,30 +229,30 @@ The `sync` method can be called anywhere you'd like to check for an update. That
The `checkForUpdate` and `getCurrentPackage` methods return promises, that when resolved, provide acces to "package" objects. The package represents your code update as well as any extra metadata (e.g. description, mandatory). The CodePush API has the distinction between the following types of packages:
* [LocalPackage](#localpackage): Represents a locally available package that either representing the currently running code or an update that hasn't been applied yet
* [LocalPackage](#localpackage): Represents a locally available package that either representing the currently running code or an update that hasn't been installed yet
* [RemotePackage](#remotepackage): Represents a remotely available package that provides an update to the app, and can be downloaded
#### LocalPackage
Contains details about an update package that has been downloaded locally or already applied (currently installed package). You can get a reference to an instance of this object either by calling the module-level `getCurrentPackage` method, or as the value of the promise returned by the `download` method of a RemotePackage.
Contains details about an update package that has been downloaded locally or already installed (currently installed package). You can get a reference to an instance of this object either by calling the module-level `getCurrentPackage` method, or as the value of the promise returned by the `download` method of a RemotePackage.
##### Properties
- __appVersion__: The native version of the application this package update is intended for. (String)
- __deploymentKey__: Deployment key of the package. (String) This is the same value that you added to your `Info.plst` file.
- __description__: Package description. (String) This is the same value that you specified in the CLI when you released the update
- __failedApply__: Indicates whether this package instance had been previously applied but was rolled back. (Boolean) The `sync` method will automatically ignore updates which have previously failed, so you only need to worry about this property if using `checkForUpdate`.
- __failedInstall__: Indicates whether this package instance had been previously installed but was rolled back. (Boolean) The `sync` method will automatically ignore updates which have previously failed, so you only need to worry about this property if using `checkForUpdate`.
- __label__: Package label. (String)
- __isMandatory__: Flag indicating if the update is mandatory. (Boolean) This is the value that you specified in the CLI when you released the update
- __packageHash__: The hash value of the package. (String)
- __packageSize__: The size of the package, in bytes. (Number)
- __isFirstRun__: Flag indicating whether this is the first time the package has been run after being applied. (Boolean) This is useful for determining whether you would like to show a "What's New?" UI to the end-user after applying an update.
- __isFirstRun__: Flag indicating whether this is the first time the package has been run after being installed. (Boolean) This is useful for determining whether you would like to show a "What's New?" UI to the end-user after installing an update.
##### Methods
- __apply(rollbackTimeout: Number = 0, restartImmediately: Boolean = true): Promise&lt;void&gt;__: Applies this package to the application by unzipping its contents (e.g. the JS bundle) and saving it to the location on disk where the runtime expects to find the latest version of the app. If the `restartImmediately` parameter is set to `false`, the apply will complete, but it won't take effect until the next time that the app is restarted. Otherwise, the app will be immediately restarted after performing the apply, so that the end-user sees the changes.
- __install(rollbackTimeout: Number = 0, installMode: CodePush.InstallMode = CodePush.InstallMode.UPDATE_ON_RESTART): Promise&lt;void&gt;__: Installs this package to the application by unzipping its contents (e.g. the JS bundle) and saving it to the location on disk where the runtime expects to find the latest version of the app. If the `restartImmediately` parameter is set to `false`, the install will complete, but it won't take effect until the next time that the app is restarted. Otherwise, the app will be immediately restarted after performing the install, so that the end-user sees the changes.
<br /><br />
If a value greater than zero is provided to the `rollbackTimeout` parameter, the application will wait for the `notifyApplicationReady` method to be called for the given number of milliseconds.
<br /><br />
Note: The "rollback timer" doesn't start until the update has actually become active. If you pass a truthy value to the `restartImmediately` parameter, then the rollback timer will also start immediately. However, if you pass a falsey value, then the rollback timer will start the next time the app starts, not at the point that you called `apply`.
Note: The "rollback timer" doesn't start until the update has actually become active. If you pass a truthy value to the `restartImmediately` parameter, then the rollback timer will also start immediately. However, if you pass a falsey value, then the rollback timer will start the next time the app starts, not at the point that you called `install`.
#### RemotePackage

View File

@@ -27,7 +27,7 @@ var UpdateButton = React.createClass({
},
update: function() {
this.state.update.download().done((newPackage) => {
newPackage.apply();
newPackage.install();
});
},
render: function() {

View File

@@ -16,7 +16,7 @@ var UpdateOnStart = React.createClass({
CodePush.checkForUpdate().done((update) => {
if (update && update.downloadUrl) {
update.download().done((newPackage) => {
newPackage.apply();
newPackage.install();
});
}
});

7
jsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"experimentalDecorators" : true
}
}

View File

@@ -36,8 +36,8 @@ module.exports = (NativeCodePush) => {
};
var local = {
apply: function apply(rollbackTimeout = 0, restartMode = NativeCodePush.codePushRestartModeImmediate) {
return NativeCodePush.applyUpdate(this, rollbackTimeout, restartMode);
install: function install(rollbackTimeout = 0, installMode = NativeCodePush.codePushInstallModeImmediate) {
return NativeCodePush.installUpdate(this, rollbackTimeout, installMode);
}
};