From f86581140144500b7f002be64c49e6efb2ddb804 Mon Sep 17 00:00:00 2001 From: Eric Schulte Date: Tue, 12 Apr 2016 14:04:51 -0400 Subject: [PATCH 01/53] remote "-beta" from podspec -- itunesconnect error --- CodePush.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodePush.podspec b/CodePush.podspec index 3e57981..e91348a 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'CodePush' - s.version = '1.10.1-beta' + s.version = '1.10.1' s.summary = 'React Native plugin for the CodePush service' s.author = 'Microsoft Corporation' s.license = 'MIT' From d1d10ba7201de32b128a81379dd25953f3b60356 Mon Sep 17 00:00:00 2001 From: Eric Schulte Date: Tue, 12 Apr 2016 17:38:04 -0400 Subject: [PATCH 02/53] -beta needed for tag name --- CodePush.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodePush.podspec b/CodePush.podspec index e91348a..e6ebbea 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -6,7 +6,7 @@ Pod::Spec.new do |s| s.author = 'Microsoft Corporation' s.license = 'MIT' s.homepage = 'http://microsoft.github.io/code-push/' - s.source = { :git => 'https://github.com/Microsoft/react-native-code-push.git', :tag => "v#{s.version}" } + s.source = { :git => 'https://github.com/Microsoft/react-native-code-push.git', :tag => "v#{s.version}-beta" } s.platform = :ios, '7.0' s.source_files = 'ios/CodePush/*.{h,m}', 'ios/CodePush/SSZipArchive/*.{h,m}', 'ios/CodePush/SSZipArchive/aes/*.{h,c}', 'ios/CodePush/SSZipArchive/minizip/*.{h,c}' s.public_header_files = 'ios/CodePush/CodePush.h' From a501a5366c11018ce7ab41cdef35ae7bc8cb325f Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 13 Apr 2016 10:21:23 -0700 Subject: [PATCH 03/53] Adding example app --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 759209a..076924d 100644 --- a/README.md +++ b/README.md @@ -720,6 +720,7 @@ Constructs the CodePush client runtime and represents the `ReactPackage` instanc The React Native community has graciously created some awesome open source apps that can serve as examples for developers that are getting started. The following is a list of OSS React Native apps that are also using CodePush, and can therefore be used to see how others are using the service: +* [F8 App](https://github.com/fbsamples/f8app) - The official conference app for [F8 2016](https://www.fbf8.com/). * [Feline for Product Hunt](https://github.com/arjunkomath/Feline-for-Product-Hunt) - An Android client for Product Hunt. * [GeoEncoding](https://github.com/LynxITDigital/GeoEncoding) - An app by [Lynx IT Digital](https://digital.lynxit.com.au) which demonstrates how to use numerous React Native components and modules. * [Math Facts](https://github.com/Khan/math-facts) - An app by Khan Academy to help memorize math facts more easily. From bd1db700aa470189e0553e78f3455dcb3b5afe2e Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 13 Apr 2016 17:40:26 -0700 Subject: [PATCH 04/53] Removing use of ReflectiveOperationException --- .../main/java/com/microsoft/codepush/react/CodePush.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 62cf129..ffc82db 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 @@ -30,7 +30,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.lang.ReflectiveOperationException; +import java.lang.Exception; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -413,7 +413,7 @@ public class CodePush implements ReactPackage { try { recreateMethod.invoke(instanceManager); } - catch (ReflectiveOperationException e) { + catch (Exception e) { // The recreation method threw an unknown exception // so just simply fallback to restarting the Activity loadBundleLegacy(); @@ -421,7 +421,7 @@ public class CodePush implements ReactPackage { } }); } - catch (ReflectiveOperationException e) { + catch (Exception e) { // Our reflection logic failed somewhere // so fall back to restarting the Activity loadBundleLegacy(); From a924ab9224fd35ec1739ab989197727ee970d960 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 13 Apr 2016 17:51:21 -0700 Subject: [PATCH 05/53] Removing superflous import --- .../app/src/main/java/com/microsoft/codepush/react/CodePush.java | 1 - 1 file changed, 1 deletion(-) 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 ffc82db..28ba77d 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 @@ -30,7 +30,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.lang.Exception; import java.lang.reflect.Field; import java.lang.reflect.Method; From e2b23e20244615a2767ced4f41a9fcb34f20379f Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 13 Apr 2016 21:45:38 -0700 Subject: [PATCH 06/53] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc46dae..f978e84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-code-push", - "version": "1.10.1-beta", + "version": "1.10.2-beta", "description": "React Native plugin for the CodePush service", "main": "CodePush.js", "homepage": "https://microsoft.github.io/code-push", From 98d1de331d94f51b1a890efa465fdc5404ba42d2 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Thu, 14 Apr 2016 08:41:23 -0700 Subject: [PATCH 07/53] Bumping podspec version --- CodePush.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodePush.podspec b/CodePush.podspec index e6ebbea..ff149f9 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'CodePush' - s.version = '1.10.1' + s.version = '1.10.2' s.summary = 'React Native plugin for the CodePush service' s.author = 'Microsoft Corporation' s.license = 'MIT' From 0a6c048b500229e345ddef71a61cf9b4c0c51953 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Thu, 14 Apr 2016 10:22:38 -0700 Subject: [PATCH 08/53] Adding note about calling sync on resume --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 076924d..9865c1a 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,17 @@ The simplest way to do this is to perform the following in your app's root compo codePush.sync(); ``` -If an update is available, it will be silently downloaded, and installed the next time the app is restarted (either explicitly by the end user or by the OS), which ensures the least invasive experience for your end users. If an available update is mandatory, then it will be installed immediately, ensuring that the end user gets it as soon as possible. Additionally, if you would like to display a confirmation dialog (an "active install"), or customize the update experience in any way, refer to the `sync` method's [API reference](#codepushsync) for information on how to tweak this default behavior. +If an update is available, it will be silently downloaded, and installed the next time the app is restarted (either explicitly by the end user or by the OS), which ensures the least invasive experience for your end users. If an available update is mandatory, then it will be installed immediately, ensuring that the end user gets it as soon as possible. + +If you would like your app to discover updates more quickly, you can also choose to call `sync` every time the app resumes from the background, by adding the following code (or something equivalent) as part of your app's startup behavior (e.g. your root component's `componentDidMount` method). You can call `sync` as frequently as you would like, so when and where you call it just depends on your personal preference. + +```javascript +AppState.addEventListener("change", (newState) => { + newState === "active" && codePush.sync(); +}); +``` + +Additionally, if you would like to display an update confirmation dialog (an "active install"), cofigure when an available update is installed (e.g. force an immediate restart) or customize the update experience in any way, refer to the `sync` method's [API reference](#codepushsync) for information on how to tweak this default behavior. *NOTE: While [Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf) fully allows performing over-the-air updates of JavaScript and assets (which is what enables CodePush!), it is against their policy for an app to display an update prompt. Because of this, we recommend that App Store-distributed apps don't enable the `updateDialog` option when calling `sync`, whereas Google Play and internally distributed apps (e.g. Enterprise, Fabric, HockeyApp) can choose to enable/customize it.* From bd09373c370285cbbf767afc4ab205e7e4762070 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Thu, 14 Apr 2016 11:52:08 -0700 Subject: [PATCH 09/53] Fixing typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9865c1a..ab132d5 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ AppState.addEventListener("change", (newState) => { }); ``` -Additionally, if you would like to display an update confirmation dialog (an "active install"), cofigure when an available update is installed (e.g. force an immediate restart) or customize the update experience in any way, refer to the `sync` method's [API reference](#codepushsync) for information on how to tweak this default behavior. +Additionally, if you would like to display an update confirmation dialog (an "active install"), configure when an available update is installed (e.g. force an immediate restart) or customize the update experience in any way, refer to the `sync` method's [API reference](#codepushsync) for information on how to tweak this default behavior. *NOTE: While [Apple's developer agreement](https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf) fully allows performing over-the-air updates of JavaScript and assets (which is what enables CodePush!), it is against their policy for an app to display an update prompt. Because of this, we recommend that App Store-distributed apps don't enable the `updateDialog` option when calling `sync`, whereas Google Play and internally distributed apps (e.g. Enterprise, Fabric, HockeyApp) can choose to enable/customize it.* From 48a49b9f8cf4c9a035fc71e6cc530548e0b1b323 Mon Sep 17 00:00:00 2001 From: Riku Ayanokoji Date: Fri, 15 Apr 2016 14:20:59 +0800 Subject: [PATCH 10/53] check expectedContentLength greater than zero before assert --- ios/CodePush/CodePushDownloadHandler.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index f7f88dc..62fd6f4 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -90,12 +90,15 @@ failCallback:(void (^)(NSError *err))failCallback { } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { - // We should have received all of the bytes if this is called. - assert(self.receivedContentLength == self.expectedContentLength); - + // expectedContentLength might be -1 when NSURLConnection don't know the length(e.g. response encode with gzip) + if (self.expectedContentLength > 0) { + // We should have received all of the bytes if this is called. + assert(self.receivedContentLength == self.expectedContentLength); + } + [self.outputFileStream close]; BOOL isZip = _header[0] == 'P' && _header[1] == 'K' && _header[2] == 3 && _header[3] == 4; self.doneCallback(isZip); } -@end \ No newline at end of file +@end From 3086aeae08b3b9b2eb916d6678c8a6b8deab25ec Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Fri, 15 Apr 2016 15:24:22 -0700 Subject: [PATCH 11/53] iOS implementation of getUpdateMetadata --- CodePush.js | 23 +++++-- ios/CodePush.xcodeproj/project.pbxproj | 12 ++-- ios/CodePush/CodePush.h | 7 +++ ios/CodePush/CodePush.m | 62 ++++++++++++------- ios/CodePush/CodePushPackage.m | 30 +++------ ios/CodePush/RCTConvert+CodePushUpdateState.m | 15 +++++ 6 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 ios/CodePush/RCTConvert+CodePushUpdateState.m diff --git a/CodePush.js b/CodePush.js index a4f5ff1..7e98762 100644 --- a/CodePush.js +++ b/CodePush.js @@ -100,12 +100,16 @@ const getConfiguration = (() => { })(); async function getCurrentPackage() { - const localPackage = await NativeCodePush.getCurrentPackage(); - if (localPackage) { - localPackage.failedInstall = await NativeCodePush.isFailedUpdate(localPackage.packageHash); - localPackage.isFirstRun = await NativeCodePush.isFirstRun(localPackage.packageHash); + return await getUpdateMetadata(CodePush.UpdateState.LATEST); +} + +async function getUpdateMetadata(updateState) { + const updateMetadata = await NativeCodePush.getUpdateMetadata(updateState || CodePush.UpdateState.RUNNING); + if (updateMetadata) { + updateMetadata.failedInstall = await NativeCodePush.isFailedUpdate(updateMetadata.packageHash); + updateMetadata.isFirstRun = await NativeCodePush.isFirstRun(updateMetadata.packageHash); } - return localPackage; + return updateMetadata; } function getPromisifiedSdk(requestFetchAdapter, config) { @@ -388,9 +392,11 @@ if (NativeCodePush) { checkForUpdate, getConfiguration, getCurrentPackage, + getUpdateMetadata, log, notifyApplicationReady, restartApp, + restartApplication: restartApp, setUpTestDependencies, sync, InstallMode: { @@ -409,6 +415,11 @@ if (NativeCodePush) { SYNC_IN_PROGRESS: 7, // There is an ongoing "sync" operation in progress. UNKNOWN_ERROR: -1 }, + UpdateState: { + RUNNING: NativeCodePush.codePushUpdateStateRunning, + PENDING: NativeCodePush.codePushUpdateStatePending, + LATEST: NativeCodePush.codePushUpdateStateLatest + }, DEFAULT_UPDATE_DIALOG: { appendReleaseDescription: false, descriptionPrefix: " Description: ", @@ -424,4 +435,4 @@ if (NativeCodePush) { log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly."); } -module.exports = CodePush; +module.exports = CodePush; \ No newline at end of file diff --git a/ios/CodePush.xcodeproj/project.pbxproj b/ios/CodePush.xcodeproj/project.pbxproj index a0772a5..eed3c75 100644 --- a/ios/CodePush.xcodeproj/project.pbxproj +++ b/ios/CodePush.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; }; 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */; }; 1B762E901C9A5E9A006EF800 /* CodePushErrorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B762E8F1C9A5E9A006EF800 /* CodePushErrorUtils.m */; }; + 1BCC09A71CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BCC09A61CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m */; }; 540D20121C7684FE00D6EF41 /* CodePushUpdateUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */; }; 5421FE311C58AD5A00986A55 /* CodePushTelemetryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */; }; 54A0026C1C0E2880004C3CEC /* aescrypt.c in Sources */ = {isa = PBXBuildFile; fileRef = 54A0024C1C0E2880004C3CEC /* aescrypt.c */; }; @@ -49,6 +50,7 @@ 13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePush.m; path = CodePush/CodePush.m; sourceTree = ""; }; 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+CodePushInstallMode.m"; path = "CodePush/RCTConvert+CodePushInstallMode.m"; sourceTree = ""; }; 1B762E8F1C9A5E9A006EF800 /* CodePushErrorUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushErrorUtils.m; path = CodePush/CodePushErrorUtils.m; sourceTree = ""; }; + 1BCC09A61CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "RCTConvert+CodePushUpdateState.m"; path = "CodePush/RCTConvert+CodePushUpdateState.m"; sourceTree = ""; }; 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushUpdateUtils.m; path = CodePush/CodePushUpdateUtils.m; sourceTree = ""; }; 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CodePushTelemetryManager.m; path = CodePush/CodePushTelemetryManager.m; sourceTree = ""; }; 54A0024A1C0E2880004C3CEC /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = ""; }; @@ -168,16 +170,17 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 54A002481C0E2880004C3CEC /* SSZipArchive */, - 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */, + 13BE3DEC1AC21097009241FE /* CodePush.h */, + 13BE3DED1AC21097009241FE /* CodePush.m */, 81D51F391B6181C2000DA084 /* CodePushConfig.m */, 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */, 1B762E8F1C9A5E9A006EF800 /* CodePushErrorUtils.m */, 810D4E6C1B96935000B397E9 /* CodePushPackage.m */, 5421FE301C58AD5A00986A55 /* CodePushTelemetryManager.m */, 540D20111C7684FE00D6EF41 /* CodePushUpdateUtils.m */, - 13BE3DEC1AC21097009241FE /* CodePush.h */, - 13BE3DED1AC21097009241FE /* CodePush.m */, + 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m */, + 1BCC09A61CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m */, + 54A002481C0E2880004C3CEC /* SSZipArchive */, 134814211AA4EA7D00B7C361 /* Products */, ); sourceTree = ""; @@ -242,6 +245,7 @@ 540D20121C7684FE00D6EF41 /* CodePushUpdateUtils.m in Sources */, 54A0026E1C0E2880004C3CEC /* aestab.c in Sources */, 54A002761C0E2880004C3CEC /* mztools.c in Sources */, + 1BCC09A71CC19EB700DDC0DD /* RCTConvert+CodePushUpdateState.m in Sources */, 54A002781C0E2880004C3CEC /* zip.c in Sources */, 54A002791C0E2880004C3CEC /* SSZipArchive.m in Sources */, 1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushInstallMode.m in Sources */, diff --git a/ios/CodePush/CodePush.h b/ios/CodePush/CodePush.h index 774fccf..b56561e 100644 --- a/ios/CodePush/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -84,6 +84,7 @@ failCallback:(void (^)(NSError *err))failCallback; + (NSString *)getBinaryAssetsPath; + (NSDictionary *)getCurrentPackage:(NSError **)error; ++ (NSDictionary *)getPreviousPackage:(NSError **)error; + (NSString *)getCurrentPackageFolderPath:(NSError **)error; + (NSString *)getCurrentPackageBundlePath:(NSError **)error; + (NSString *)getCurrentPackageHash:(NSError **)error; @@ -140,4 +141,10 @@ typedef NS_ENUM(NSInteger, CodePushInstallMode) { CodePushInstallModeImmediate, CodePushInstallModeOnNextRestart, CodePushInstallModeOnNextResume +}; + +typedef NS_ENUM(NSInteger, CodePushUpdateState) { + CodePushUpdateStateRunning, + CodePushUpdateStatePending, + CodePushUpdateStateLatest }; \ No newline at end of file diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 7b69dc2..0f73662 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -180,12 +180,16 @@ static NSString *bundleResourceName = @"main"; */ - (NSDictionary *)constantsToExport { - // Export the values of the CodePushInstallMode enum - // so that the script-side can easily stay in sync + // Export the values of the CodePushInstallMode and CodePushUpdateState + // enums so that the script-side can easily stay in sync return @{ @"codePushInstallModeOnNextRestart":@(CodePushInstallModeOnNextRestart), @"codePushInstallModeImmediate": @(CodePushInstallModeImmediate), - @"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume) + @"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume), + + @"codePushUpdateStateRunning": @(CodePushUpdateStateRunning), + @"codePushUpdateStatePending": @(CodePushUpdateStatePending), + @"codePushUpdateStateLatest": @(CodePushUpdateStateLatest) }; }; @@ -532,35 +536,49 @@ RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve } /* - * This method is the native side of the CodePush.getCurrentPackage method. + * This method is the native side of the CodePush.getUpdateMetadata method. */ -RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve +RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState + resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSError *error; NSMutableDictionary *package = [[CodePushPackage getCurrentPackage:&error] mutableCopy]; if (error) { - reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); - return; + return reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); } else if (package == nil) { + // The app hasn't downloaded any CodePush updates yet, + // so we simply return nil regardless if the user + // wanted to retrieve the pending or running update. + return resolve(nil); + } + + // We have a CodePush update, so let's see if it's currently in a pending state. + BOOL currentUpdateIsPending = [self isPendingUpdate:[package objectForKey:PackageHashKey]]; + + if (updateState == CodePushUpdateStatePending && !currentUpdateIsPending) { + // The caller wanted a pending update + // but there isn't currently one. resolve(nil); - return; + } else if (updateState == CodePushUpdateStateRunning && currentUpdateIsPending) { + // The caller wants the running update, but the current + // one is pending, so we need to grab the previous. + resolve([CodePushPackage getPreviousPackage:nil]); + } else { + // The current package satisfies the request: + // 1) Caller wanted a pending, and there is a pending update + // 2) Caller wanted the running update, and there isn't a pending + // 3) Calers wants the latest update, regardless if it's pending or not + if (isRunningBinaryVersion) { + // This only matters in Debug builds. Since we do not clear "outdated" updates, + // we need to indicate to the JS side that somehow we have a current update on + // disk that is not actually running. + [package setObject:@(YES) forKey:@"_isDebugOnly"]; + } + + resolve(package); } - - if (isRunningBinaryVersion) { - // This only matters in Debug builds. Since we do not clear "outdated" updates, - // we need to indicate to the JS side that somehow we have a current update on - // disk that is not actually running. - [package setObject:@(YES) forKey:@"_isDebugOnly"]; - } - - // Add the "isPending" virtual property to the package at this point, so that - // the script-side doesn't need to immediately call back into native to populate it. - BOOL isPendingUpdate = [self isPendingUpdate:[package objectForKey:PackageHashKey]]; - [package setObject:@(isPendingUpdate) forKey:PackageIsPendingKey]; - - resolve(package); } /* diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index cba883f..1cd9258 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -17,7 +17,6 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (void)clearUpdates { [[NSFileManager defaultManager] removeItemAtPath:[self getCodePushPath] error:nil]; - [[NSFileManager defaultManager] removeItemAtPath:[self getStatusFilePath] error:nil]; } + (void)downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl @@ -290,27 +289,8 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getCurrentPackage:(NSError **)error { - NSString *folderPath = [CodePushPackage getCurrentPackageFolderPath:error]; - if (!*error) { - if (!folderPath) { - return nil; - } - - NSString *packagePath = [folderPath stringByAppendingPathComponent:@"app.json"]; - NSString *content = [NSString stringWithContentsOfFile:packagePath - encoding:NSUTF8StringEncoding - error:error]; - if (!*error) { - NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary* jsonDict = [NSJSONSerialization JSONObjectWithData:data - options:kNilOptions - error:error]; - - return jsonDict; - } - } - - return nil; + NSString *packageHash = [CodePushPackage getCurrentPackageHash:error]; + return [CodePushPackage getPackage:packageHash error:error]; } + (NSString *)getCurrentPackageBundlePath:(NSError **)error @@ -423,6 +403,12 @@ static NSString *const UnzippedFolderName = @"unzipped"; return [[self getCodePushPath] stringByAppendingPathComponent:packageHash]; } ++ (NSDictionary *)getPreviousPackage:(NSError **)error +{ + NSString *packageHash = [CodePushPackage getPreviousPackageHash:error]; + return [CodePushPackage getPackage:packageHash error:error]; +} + + (NSString *)getPreviousPackageHash:(NSError **)error { NSDictionary *info = [self getCurrentPackageInfo:error]; diff --git a/ios/CodePush/RCTConvert+CodePushUpdateState.m b/ios/CodePush/RCTConvert+CodePushUpdateState.m new file mode 100644 index 0000000..7585124 --- /dev/null +++ b/ios/CodePush/RCTConvert+CodePushUpdateState.m @@ -0,0 +1,15 @@ +#import "CodePush.h" +#import "RCTConvert.h" + +// Extending the RCTConvert class allows the React Native +// bridge to handle args of type "CodePushUpdateState" +@implementation RCTConvert (CodePushUpdateState) + +RCT_ENUM_CONVERTER(CodePushUpdateState, (@{ @"codePushUpdateStateRunning": @(CodePushUpdateStateRunning), + @"codePushUpdateStatePending": @(CodePushUpdateStatePending), + @"codePushUpdateStateLatest": @(CodePushUpdateStateLatest) + }), + CodePushUpdateStateRunning, // Default enum value + integerValue) + +@end \ No newline at end of file From 4bea6847e83c3fa908efc0d8d1845aed3edf69d7 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Fri, 15 Apr 2016 17:11:30 -0700 Subject: [PATCH 12/53] Set isPending --- ios/CodePush/CodePush.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 0f73662..1fc223d 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -544,7 +544,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState { NSError *error; NSMutableDictionary *package = [[CodePushPackage getCurrentPackage:&error] mutableCopy]; - + if (error) { return reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); } else if (package == nil) { @@ -570,13 +570,17 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState // 1) Caller wanted a pending, and there is a pending update // 2) Caller wanted the running update, and there isn't a pending // 3) Calers wants the latest update, regardless if it's pending or not + if (isRunningBinaryVersion) { // This only matters in Debug builds. Since we do not clear "outdated" updates, // we need to indicate to the JS side that somehow we have a current update on // disk that is not actually running. [package setObject:@(YES) forKey:@"_isDebugOnly"]; } - + + // To support differentiating pending vs. non-pending updates + // when request an update state of LATEST, provide an isPending flag + [package setObject:@(currentUpdateIsPending) forKey:PackageIsPendingKey]; resolve(package); } } From 3026c865562b5890561936a3723d306d5ae4f8ef Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sat, 16 Apr 2016 11:21:41 -0700 Subject: [PATCH 13/53] Add error handling --- ios/CodePush/CodePushPackage.m | 53 ++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index 1cd9258..dd89368 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -10,6 +10,7 @@ static NSString *const DownloadFileName = @"download.zip"; static NSString *const RelativeBundlePathKey = @"bundlePath"; static NSString *const StatusFile = @"codepush.json"; static NSString *const UpdateBundleFileName = @"app.jsbundle"; +static NSString *const UpdateMetadataFileName = @"app.json"; static NSString *const UnzippedFolderName = @"unzipped"; #pragma mark - Public methods @@ -46,7 +47,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; { NSString *newUpdateHash = updatePackage[@"packageHash"]; NSString *newUpdateFolderPath = [self getPackageFolderPath:newUpdateHash]; - NSString *newUpdateMetadataPath = [newUpdateFolderPath stringByAppendingPathComponent:@"app.json"]; + NSString *newUpdateMetadataPath = [newUpdateFolderPath stringByAppendingPathComponent:UpdateMetadataFileName]; NSError *error; if ([[NSFileManager defaultManager] fileExistsAtPath:newUpdateFolderPath]) { @@ -290,6 +291,11 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getCurrentPackage:(NSError **)error { NSString *packageHash = [CodePushPackage getCurrentPackageHash:error]; + + if (*error || !packageHash) { + return nil; + } + return [CodePushPackage getPackage:packageHash error:error]; } @@ -318,8 +324,8 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSString *)getCurrentPackageHash:(NSError **)error { NSDictionary *info = [self getCurrentPackageInfo:error]; - if (*error) { - return NULL; + if (*error || !info) { + return nil; } return info[@"currentPackage"]; @@ -353,7 +359,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; encoding:NSUTF8StringEncoding error:error]; if (*error) { - return NULL; + return nil; } NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; @@ -361,7 +367,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; options:kNilOptions error:error]; if (*error) { - return NULL; + return nil; } return [json mutableCopy]; @@ -375,27 +381,25 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getPackage:(NSString *)packageHash error:(NSError **)error { - NSString *folderPath = [self getPackageFolderPath:packageHash]; + NSString *updateDirectoryPath = [self getPackageFolderPath:packageHash]; + NSString *updateMetadataFilePath = [updateDirectoryPath stringByAppendingPathComponent:UpdateMetadataFileName]; - if (!folderPath) { - return [NSDictionary dictionary]; + if (![[NSFileManager defaultManager] fileExistsAtPath:updateMetadataFilePath]) { + return nil; } - NSString *packageFilePath = [folderPath stringByAppendingPathComponent:@"app.json"]; + NSString *updateMetadataString = [NSString stringWithContentsOfFile:updateMetadataFilePath + encoding:NSUTF8StringEncoding + error:error]; - NSString *content = [NSString stringWithContentsOfFile:packageFilePath - encoding:NSUTF8StringEncoding - error:error]; - if (!*error) { - NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary* jsonDict = [NSJSONSerialization JSONObjectWithData:data - options:kNilOptions - error:error]; - - return jsonDict; + if (*error) { + return nil; } - return NULL; + NSData *updateMetadata = [updateMetadataString dataUsingEncoding:NSUTF8StringEncoding]; + return [NSJSONSerialization JSONObjectWithData:updateMetadata + options:kNilOptions + error:error]; } + (NSString *)getPackageFolderPath:(NSString *)packageHash @@ -405,7 +409,12 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getPreviousPackage:(NSError **)error { - NSString *packageHash = [CodePushPackage getPreviousPackageHash:error]; + NSString *packageHash = [self getPreviousPackageHash:error]; + + if (*error || !packageHash) { + return nil; + } + return [CodePushPackage getPackage:packageHash error:error]; } @@ -413,7 +422,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; { NSDictionary *info = [self getCurrentPackageInfo:error]; if (*error) { - return NULL; + return nil; } return info[@"previousPackage"]; From 303edd1f6a8312586fb1e8e122d0aa6c18474db0 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sat, 16 Apr 2016 13:40:24 -0700 Subject: [PATCH 14/53] Android implementation --- .../microsoft/codepush/react/CodePush.java | 48 +++++++++++++++---- .../codepush/react/CodePushPackage.java | 24 +++++----- .../codepush/react/CodePushUpdateState.java | 15 ++++++ 3 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java 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 28ba77d..b6526da 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 @@ -353,9 +353,15 @@ public class CodePush implements ReactPackage { @Override public Map getConstants() { final Map constants = new HashMap<>(); + constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue()); constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue()); constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue()); + + constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue()); + constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue()); + constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue()); + return constants; } @@ -481,31 +487,53 @@ public class CodePush implements ReactPackage { promise.resolve(configMap); } - + @ReactMethod - public void getCurrentPackage(final Promise promise) { + public void getUpdateMetadata(final int updateState, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { WritableMap currentPackage = codePushPackage.getCurrentPackage(); + if (currentPackage == null) { promise.resolve(""); return null; } - if (isRunningBinaryVersion) { - currentPackage.putBoolean("_isDebugOnly", true); - } - - Boolean isPendingUpdate = false; + Boolean currentUpdateIsPending = false; if (currentPackage.hasKey(PACKAGE_HASH_KEY)) { String currentHash = currentPackage.getString(PACKAGE_HASH_KEY); - isPendingUpdate = CodePush.this.isPendingUpdate(currentHash); + currentUpdateIsPending = CodePush.this.isPendingUpdate(currentHash); } + + if (updateState == CodePushUpdateState.PENDING.getValue() && !currentUpdateIsPending) { + // The caller wanted a pending update + // but there isn't currently one. + promise.resolve(""); + } else if (updateState == CodePushUpdateState.RUNNING.getValue() && currentUpdateIsPending) { + // The caller wants the running update, but the current + // one is pending, so we need to grab the previous. + promise.resolve(codePushPackage.getPreviousPackage()); + } else { + // The current package satisfies the request: + // 1) Caller wanted a pending, and there is a pending update + // 2) Caller wanted the running update, and there isn't a pending + // 3) Calers wants the latest update, regardless if it's pending or not + + if (isRunningBinaryVersion) { + // This only matters in Debug builds. Since we do not clear "outdated" updates, + // we need to indicate to the JS side that somehow we have a current update on + // disk that is not actually running. + currentPackage.putBoolean("_isDebugOnly", true); + } - currentPackage.putBoolean("isPending", isPendingUpdate); - promise.resolve(currentPackage); + // To support differentiating pending vs. non-pending updates + // when request an update state of LATEST, provide an isPending flag + currentPackage.putBoolean("isPending", currentUpdateIsPending); + promise.resolve(currentPackage); + } + return null; } }; 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 fb048c3..6fefb5e 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 @@ -18,7 +18,6 @@ import java.net.URL; import java.nio.ByteBuffer; public class CodePushPackage { - private final String CODE_PUSH_FOLDER_PREFIX = "CodePush"; private final String CURRENT_PACKAGE_KEY = "currentPackage"; private final String DIFF_MANIFEST_FILE_NAME = "hotcodepush.json"; @@ -124,18 +123,21 @@ public class CodePushPackage { } public WritableMap getCurrentPackage() { - String folderPath = getCurrentPackageFolderPath(); - if (folderPath == null) { + String packageHash = getCurrentPackageHash(); + if (packageHash == null) { return null; } - - String packagePath = CodePushUtils.appendPathComponent(folderPath, PACKAGE_FILE_NAME); - try { - return CodePushUtils.getWritableMapFromFile(packagePath); - } catch (IOException e) { - // Should not happen unless the update metadata was somehow deleted. + + return getPackage(packageHash); + } + + public WritableMap getPreviousPackage() { + String packageHash = getPreviousPackageHash(); + if (packageHash == null) { return null; } + + return getPackage(packageHash); } public WritableMap getPackage(String packageHash) { @@ -340,8 +342,6 @@ public class CodePushPackage { } public void clearUpdates() { - File statusFile = new File(getStatusFilePath()); - statusFile.delete(); FileUtils.deleteDirectoryAtPath(getCodePushPath()); } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java new file mode 100644 index 0000000..21bf213 --- /dev/null +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateState.java @@ -0,0 +1,15 @@ +package com.microsoft.codepush.react; + +public enum CodePushUpdateState { + RUNNING(0), + PENDING(1), + LATEST(2); + + private final int value; + CodePushUpdateState(int value) { + this.value = value; + } + public int getValue() { + return this.value; + } +} \ No newline at end of file From a3bd17a108f0db0340e06a9ae4d6af8b1bbe9ea6 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sat, 16 Apr 2016 14:36:19 -0700 Subject: [PATCH 15/53] Doc and .d.ts updates --- README.md | 49 +++++++++++++++++++++++++------------ react-native-code-push.d.ts | 36 ++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ab132d5..d95d6f9 100644 --- a/README.md +++ b/README.md @@ -378,7 +378,7 @@ When you require `react-native-code-push`, the module object provides the follow * [checkForUpdate](#codepushcheckforupdate): Asks the CodePush service whether the configured app deployment has an update available. -* [getCurrentPackage](#codepushgetcurrentpackage): Retrieves the metadata about the currently installed update (e.g. description, installation time, size). +* [getUpdateMetadata](#codepushgetupdatemetadata): Retrieves the metadata for an installed update (e.g. description, mandatory). * [notifyApplicationReady](#codepushnotifyapplicationready): Notifies the CodePush runtime that an installed update is considered successful. If you are manually checking for and installing updates (i.e. not using the [sync](#codepushsync) method to handle it all for you), then this method **MUST** be called; otherwise CodePush will treat the update as failed and rollback to the previous version when the app next restarts. @@ -417,33 +417,41 @@ codePush.checkForUpdate() }); ``` -#### codePush.getCurrentPackage +#### codePush.getUpdateMetadata ```javascript -codePush.getCurrentPackage(): Promise; +codePush.getUpdateMetadata(updateState: UpdateState = UpdateState.RUNNING): Promise; ``` -Retrieves the metadata about the currently installed "package" (e.g. description, installation time). This can be useful for scenarios such as displaying a "what's new?" dialog after an update has been applied or checking whether there is a pending update that is waiting to be applied via a resume or restart. +Retrieves the metadata for an installed update (e.g. description, mandatory) whose state matches the specified `updateState` parameter. This can be useful for scenarios such as displaying a "what's new?" dialog after an update has been applied or checking whether there is a pending update that is waiting to be applied via a resume or restart. For more details about the possible update states, and what they represent, refer to the [UpdateState reference](#updatestate). This method returns a `Promise` which resolves to one of two possible values: -1. `null` if the app is currently running the JS bundle from the binary and not a CodePush update. This occurs in the following scenarios: +1. `null` if an update with the specified state doesn't currently exist. This occurs in the following scenarios: - 1. The end-user installed the app binary and has yet to install a CodePush update + 1. The end-user hasn't installed any CodePush updates yet, and therefore, no metadata is available for any updates, regardless what you specify as the `updateState` parameter. 1. The end-user installed an update of the binary (e.g. from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary. + 1. The `updateState` parameter is set to `UpdateState.RUNNING`, but the app isn't currently running a CodePush update. There may be a pending update, which requires an app restart to become active. + 1. The `updateState` parameter is set to `UpdateState.PENDING`, but the app doesn't have any pending updates. -2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently running CodePush update. +2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently requested CodePush update (either the running or pending). Example Usage: ```javascript -codePush.getCurrentPackage() -.then((update) => { - // If the current app "session" represents the first time - // this update has run, and it had a description provided - // with it upon release, let's show it to the end user - if (update.isFirstRun && update.description) { - // Display a "what's new?" modal +// Check if there is currently a CodePush update running, and if +// so, register it with the HockeyApp SDK (https://github.com/slowpath/react-native-hockeyapp) +// so that crash reports will correctly display the JS bundle version the user was running. +codePush.getUpdateMetadata().then((update) => { + if (update) { + hockeyApp.addMetadata({ CodePushRelease: update.label }); + } +}); + +// Check to see if there is still an update pending. +codePush.getUpdateMetadata(UpdateState.PENDING).then((update) => { + if (update) { + // There's a pending update, do we want to force a restart? } }); ``` @@ -486,7 +494,6 @@ This method provides support for two different (but customizable) "modes" to eas 2. **Active mode**, which when an update is available, prompts the end user for permission before downloading it, and then immediately applies the update. If an update was released using the `mandatory` flag, the end user would still be notified about the update, but they wouldn't have the choice to ignore it. - Example Usage: ```javascript @@ -650,7 +657,7 @@ The CodePush API includes the following enums which can be used to customize the ##### InstallMode -This enum specified when you would like an installed update to actually be applied, and can be passed to either the `sync` or `LocalPackage.install` methods. It includes the following values: +This enum specifies when you would like an installed update to actually be applied, and can be passed to either the `sync` or `LocalPackage.install` methods. It includes the following values: * __codePush.InstallMode.IMMEDIATE__ *(0)* - Indicates that you want to install the update and restart the app immediately. This value is appropriate for debugging scenarios as well as when displaying an update prompt to the user, since they would expect to see the changes immediately after accepting the installation. Additionally, this mode can be used to enforce mandatory updates, since it removes the potentially undesired latency between the update installation and the next time the end user restarts or resumes the app. @@ -672,6 +679,16 @@ This enum is provided to the `syncStatusChangedCallback` function that can be pa * __codePush.SyncStatus.SYNC_IN_PROGRESS__ *(7)* - There is an ongoing `sync` operation running which prevents the current call from being executed. * __codePush.SyncStatus.UNKNOWN_ERROR__ *(-1)* - The sync operation encountered an unknown error. +##### UpdateState + +This enum specifies the state that an update is currently in, and can be specified when calling the `getUpdateMetadata` method. It includes the following values: + +* __codePush.UpdateState.RUNNING__ *(0)* - Indicates that an update represents the version of the app that is currently running. This can be useful for identifying attributes about the app, for scenarios such as displaying the release description in a "what's new?" dialog or reporting the latest version to an analytics and/or crash reporting service. + +* __codePush.UpdateState.PENDING__ *(1)* - Indicates than an update has been installed, but the app hasn't been restarted yet in order to apply it. This can be useful for determining whether there is a pending update, which you may want to force a programmatic restart (via `restartApp`) in order to apply. + +* __codePush.UpdateState.LATEST__ *(2)* - Indicates than an update represents the latest available release, and can be either currently running or pending. + ### Objective-C API Reference (iOS) The Objective-C API is made available by importing the `CodePush.h` header into your `AppDelegate.m` file, and consists of a single public class named `CodePush`. diff --git a/react-native-code-push.d.ts b/react-native-code-push.d.ts index 283b48f..9b04317 100644 --- a/react-native-code-push.d.ts +++ b/react-native-code-push.d.ts @@ -193,11 +193,13 @@ declare namespace CodePush { * @param deploymentKey The deployment key to use to query the CodePush server for an update. */ function checkForUpdate(deploymentKey?: string): ReactNativePromise; - + /** - * Retrieves the metadata about the currently installed update (e.g. description, installation time, size). + * Retrieves the metadata for an installed update (e.g. description, mandatory). + * + * @param updateState The state of the update you want to retrieve the metadata for. Defaults to UpdateState.RUNNING. */ - function getCurrentPackage(): ReactNativePromise; + function getUpdateMetadata(updateState?: UpdateState) : ReactNativePromise; /** * Notifies the CodePush runtime that an installed update is considered successful. @@ -218,7 +220,7 @@ declare namespace CodePush { * @param syncStatusChangedCallback An optional callback that allows tracking the status of the sync operation, as opposed to simply checking the resolved state via the returned Promise. * @param downloadProgressCallback An optional callback that allows tracking the progress of an update while it is being downloaded. */ - function sync(options?: SyncOptions, syncStatusChangedCallback?: SyncStatusChangedCallback, downloadProgressCallback?: DowloadProgressCallback): __React.Promise; + function sync(options?: SyncOptions, syncStatusChangedCallback?: SyncStatusChangedCallback, downloadProgressCallback?: DowloadProgressCallback): ReactNativePromise; /** * Indicates when you would like an installed update to actually be applied. @@ -241,6 +243,9 @@ declare namespace CodePush { ON_NEXT_RESUME } + /** + * Indicates the current status of a sync operation. + */ enum SyncStatus { /** * The CodePush server is being queried for an update. @@ -291,6 +296,29 @@ declare namespace CodePush { */ UNKNOWN_ERROR } + + /** + * Indicates the state that an update is currently in. + */ + enum UpdateState { + /** + * Indicates that an update represents the + * version of the app that is currently running. + */ + RUNNING, + + /** + * Indicates than an update has been installed, but the + * app hasn't been restarted yet in order to apply it. + */ + PENDING, + + /** + * Indicates than an update represents the latest available + * release, and can be either currently running or pending. + */ + LATEST + } } declare module "react-native-code-push" { From fb9a07aa2aa70cea39549e09e485b89e83196d33 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sun, 17 Apr 2016 11:45:29 +0200 Subject: [PATCH 16/53] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab132d5..1fab31a 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ Additionally, if you would like to display an update confirmation dialog (an "ac ## Releasing Updates -Once your app has been configured and distributed to your users, and you've made some JS and/or asset changes, it's time to instantly release them! The simplest (and recommended) way to do this is to use the `release-react` comand in the CodePush CLI, which will handle bundling your JavaScript and asset files and releasing the update to the CodePush server. +Once your app has been configured and distributed to your users, and you've made some JS and/or asset changes, it's time to instantly release them! The simplest (and recommended) way to do this is to use the `release-react` command in the CodePush CLI, which will handle bundling your JavaScript and asset files and releasing the update to the CodePush server. In it's most basic form, this command only requires two parameters: your app name and the platform you are bundling the update for (either `ios` or `android`). From c7b6dd28911d0b4882d1554e99cfae1e81f831cb Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 18 Apr 2016 09:45:14 -0700 Subject: [PATCH 17/53] Fixing typos --- CodePush.js | 4 ++-- README.md | 2 +- .../main/java/com/microsoft/codepush/react/CodePush.java | 6 ++---- ios/CodePush/CodePush.m | 6 ++---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/CodePush.js b/CodePush.js index 7e98762..16ff92d 100644 --- a/CodePush.js +++ b/CodePush.js @@ -106,8 +106,8 @@ async function getCurrentPackage() { async function getUpdateMetadata(updateState) { const updateMetadata = await NativeCodePush.getUpdateMetadata(updateState || CodePush.UpdateState.RUNNING); if (updateMetadata) { - updateMetadata.failedInstall = await NativeCodePush.isFailedUpdate(updateMetadata.packageHash); - updateMetadata.isFirstRun = await NativeCodePush.isFirstRun(updateMetadata.packageHash); + updateMetadata.failedInstall = await NativeCodePush.isFailedUpdate(updateMetadata.packageHash); + updateMetadata.isFirstRun = await NativeCodePush.isFirstRun(updateMetadata.packageHash); } return updateMetadata; } diff --git a/README.md b/README.md index d95d6f9..27e4c05 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ Example Usage: ```javascript // Check if there is currently a CodePush update running, and if // so, register it with the HockeyApp SDK (https://github.com/slowpath/react-native-hockeyapp) -// so that crash reports will correctly display the JS bundle version the user was running. +// so that crash reports will correctly display the JS bundle version the user was running. codePush.getUpdateMetadata().then((update) => { if (update) { hockeyApp.addMetadata({ CodePushRelease: update.label }); 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 b6526da..e4ee090 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 @@ -519,8 +519,7 @@ public class CodePush implements ReactPackage { // The current package satisfies the request: // 1) Caller wanted a pending, and there is a pending update // 2) Caller wanted the running update, and there isn't a pending - // 3) Calers wants the latest update, regardless if it's pending or not - + // 3) Caller wants the latest update, regardless if it's pending or not if (isRunningBinaryVersion) { // This only matters in Debug builds. Since we do not clear "outdated" updates, // we need to indicate to the JS side that somehow we have a current update on @@ -528,8 +527,7 @@ public class CodePush implements ReactPackage { currentPackage.putBoolean("_isDebugOnly", true); } - // To support differentiating pending vs. non-pending updates - // when request an update state of LATEST, provide an isPending flag + // Enable differentiating pending vs. non-pending updates currentPackage.putBoolean("isPending", currentUpdateIsPending); promise.resolve(currentPackage); } diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 1fc223d..e57233b 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -569,8 +569,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState // The current package satisfies the request: // 1) Caller wanted a pending, and there is a pending update // 2) Caller wanted the running update, and there isn't a pending - // 3) Calers wants the latest update, regardless if it's pending or not - + // 3) Caller wants the latest update, regardless if it's pending or not if (isRunningBinaryVersion) { // This only matters in Debug builds. Since we do not clear "outdated" updates, // we need to indicate to the JS side that somehow we have a current update on @@ -578,8 +577,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState [package setObject:@(YES) forKey:@"_isDebugOnly"]; } - // To support differentiating pending vs. non-pending updates - // when request an update state of LATEST, provide an isPending flag + // Enable differentiating pending vs. non-pending updates [package setObject:@(currentUpdateIsPending) forKey:PackageIsPendingKey]; resolve(package); } From 4e631859cd4a5e39623feba7d7718f82d7a4c47a Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 18 Apr 2016 10:06:17 -0700 Subject: [PATCH 18/53] Alias notifyAppReady --- CodePush.js | 88 ++++++++++++++++++------------------- react-native-code-push.d.ts | 2 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CodePush.js b/CodePush.js index 16ff92d..e31558c 100644 --- a/CodePush.js +++ b/CodePush.js @@ -387,52 +387,52 @@ let CodePush; // and therefore, it doesn't make sense initializing // the JS interface when it wouldn't work anyways. if (NativeCodePush) { - CodePush = { - AcquisitionSdk: Sdk, - checkForUpdate, - getConfiguration, - getCurrentPackage, - getUpdateMetadata, - log, - notifyApplicationReady, - restartApp, - restartApplication: restartApp, - setUpTestDependencies, - sync, - 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: { - CHECKING_FOR_UPDATE: 0, - AWAITING_USER_ACTION: 1, - DOWNLOADING_PACKAGE: 2, - INSTALLING_UPDATE: 3, - UP_TO_DATE: 4, // The running app is up-to-date - UPDATE_IGNORED: 5, // The app had an optional update and the end-user chose to ignore it - UPDATE_INSTALLED: 6, // The app had an optional/mandatory update that was successfully downloaded and is about to be installed. - SYNC_IN_PROGRESS: 7, // There is an ongoing "sync" operation in progress. - UNKNOWN_ERROR: -1 - }, - UpdateState: { - RUNNING: NativeCodePush.codePushUpdateStateRunning, - PENDING: NativeCodePush.codePushUpdateStatePending, - LATEST: NativeCodePush.codePushUpdateStateLatest - }, - 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?", - title: "Update available" - } + CodePush = { + AcquisitionSdk: Sdk, + checkForUpdate, + getConfiguration, + getCurrentPackage, + getUpdateMetadata, + log, + notifyAppReady: notifyApplicationReady, + notifyApplicationReady, + restartApp, + setUpTestDependencies, + sync, + 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: { + CHECKING_FOR_UPDATE: 0, + AWAITING_USER_ACTION: 1, + DOWNLOADING_PACKAGE: 2, + INSTALLING_UPDATE: 3, + UP_TO_DATE: 4, // The running app is up-to-date + UPDATE_IGNORED: 5, // The app had an optional update and the end-user chose to ignore it + UPDATE_INSTALLED: 6, // The app had an optional/mandatory update that was successfully downloaded and is about to be installed. + SYNC_IN_PROGRESS: 7, // There is an ongoing "sync" operation in progress. + UNKNOWN_ERROR: -1 + }, + UpdateState: { + RUNNING: NativeCodePush.codePushUpdateStateRunning, + PENDING: NativeCodePush.codePushUpdateStatePending, + LATEST: NativeCodePush.codePushUpdateStateLatest + }, + 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?", + title: "Update available" } + }; } else { - log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly."); + log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly."); } module.exports = CodePush; \ No newline at end of file diff --git a/react-native-code-push.d.ts b/react-native-code-push.d.ts index 9b04317..913c06a 100644 --- a/react-native-code-push.d.ts +++ b/react-native-code-push.d.ts @@ -204,7 +204,7 @@ declare namespace CodePush { /** * Notifies the CodePush runtime that an installed update is considered successful. */ - function notifyApplicationReady(): ReactNativePromise; + function notifyAppReady(): ReactNativePromise; /** * Immediately restarts the app. From 44153a878c0766ca9be00189920c6532942b97b3 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 18 Apr 2016 12:07:03 -0700 Subject: [PATCH 19/53] Spacing fix --- ios/CodePush/CodePush.m | 2 +- ios/CodePush/CodePushPackage.m | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index e57233b..7312d56 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -564,7 +564,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState } else if (updateState == CodePushUpdateStateRunning && currentUpdateIsPending) { // The caller wants the running update, but the current // one is pending, so we need to grab the previous. - resolve([CodePushPackage getPreviousPackage:nil]); + resolve([CodePushPackage getPreviousPackage:&error]); } else { // The current package satisfies the request: // 1) Caller wanted a pending, and there is a pending update diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index dd89368..d72f30b 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -291,7 +291,6 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getCurrentPackage:(NSError **)error { NSString *packageHash = [CodePushPackage getCurrentPackageHash:error]; - if (*error || !packageHash) { return nil; } @@ -410,7 +409,6 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (NSDictionary *)getPreviousPackage:(NSError **)error { NSString *packageHash = [self getPreviousPackageHash:error]; - if (*error || !packageHash) { return nil; } From d218c32a4c2f1e899294e9ded7597f1b0f9be50b Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 18 Apr 2016 14:14:18 -0700 Subject: [PATCH 20/53] Fixing references to getCurrentPackage --- README.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d7b2c84..5e0658a 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ We try our best to maintain backwards compatability of our plugin with previous | <0.14.0 | **Unsupported** | | v0.14.0 | v1.3.0 *(introduced Android support)* | | v0.15.0-v0.18.0 | v1.4.0-v1.6.0 *(introduced iOS asset support)* | -| v0.19.0-v0.24.0 | v1.7.0+ *(introduced Android asset support)* | -| v0.25.0+ | TBD :) We work hard to respond to new RN releases, but they do occasionally break us. We will update this chart with each RN release, so that users can check to see what our "official" support is. +| v0.19.0-v0.25.0 | v1.7.0+ *(introduced Android asset support)* | +| v0.26.0+ | TBD :) We work hard to respond to new RN releases, but they do occasionally break us. We will update this chart with each RN release, so that users can check to see what our "official" support is. ## Supported Components @@ -430,9 +430,12 @@ This method returns a `Promise` which resolves to one of two possible values: 1. `null` if an update with the specified state doesn't currently exist. This occurs in the following scenarios: 1. The end-user hasn't installed any CodePush updates yet, and therefore, no metadata is available for any updates, regardless what you specify as the `updateState` parameter. - 1. The end-user installed an update of the binary (e.g. from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary. - 1. The `updateState` parameter is set to `UpdateState.RUNNING`, but the app isn't currently running a CodePush update. There may be a pending update, which requires an app restart to become active. - 1. The `updateState` parameter is set to `UpdateState.PENDING`, but the app doesn't have any pending updates. + + 2. The end-user installed an update of the binary (e.g. from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary. Therefore, it would exhibit the same behavior as #1 + + 3. The `updateState` parameter is set to `UpdateState.RUNNING`, but the app isn't currently running a CodePush update. There may be a pending update, but the app hasn't been restarted yet in order to make it active. + + 4. The `updateState` parameter is set to `UpdateState.PENDING`, but the app doesn't have any currently pending updates. 2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently requested CodePush update (either the running or pending). @@ -456,15 +459,17 @@ codePush.getUpdateMetadata(UpdateState.PENDING).then((update) => { }); ``` -#### codePush.notifyApplicationReady +#### codePush.notifyAppReady ```javascript -codePush.notifyApplicationReady(): Promise; +codePush.notifyAppReady(): Promise; ``` Notifies the CodePush runtime that a freshly installed update should be considered successful, and therefore, an automatic client-side rollback isn't necessary. It is mandatory to call this function somewhere in the code of the updated bundle. Otherwise, when the app next restarts, the CodePush runtime will assume that the installed update has failed and roll back to the previous version. This behavior exists to help ensure that your end users aren't blocked by a broken update. -If you are using the `sync` function, and doing your update check on app start, then you don't need to manually call `notifyApplicationReady` since `sync` will call it for you. This behavior exists due to the assumption that the point at which `sync` is called in your app represents a good approximation of a successful startup. +If you are using the `sync` function, and doing your update check on app start, then you don't need to manually call `notifyAppReady` since `sync` will call it for you. This behavior exists due to the assumption that the point at which `sync` is called in your app represents a good approximation of a successful startup. + +*NOTE: This method is also aliased as `notifyApplicationReady` (for backwards compatibility).* #### codePush.restartApp @@ -611,7 +616,7 @@ The `sync` method can be called anywhere you'd like to check for an update. That #### Package objects -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: +The `checkForUpdate` and `getUpdateMetadata` methods return `Promise` objects, 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 downloaded update that is either already running, or has been installed and is pending an app restart. @@ -619,7 +624,7 @@ The `checkForUpdate` and `getCurrentPackage` methods return promises, that when ##### LocalPackage -Contains details about an update that has been downloaded locally or already installed. 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 `RemotePackage.download` method. +Contains details about an update that has been downloaded locally or already installed. You can get a reference to an instance of this object either by calling the module-level `getUpdateMetadata` method, or as the value of the promise returned by the `RemotePackage.download` method. ###### Properties - __appVersion__: The app binary version that this update is dependent on. This is the value that was specified via the `appStoreVersion` parameter when calling the CLI's `release` command. *(String)* From 165c68a548f90486873a29f3418966ecfad0fdae Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 18 Apr 2016 21:09:06 -0700 Subject: [PATCH 21/53] Fixing restart in debug --- .../microsoft/codepush/react/CodePush.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) 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 e4ee090..5722ee5 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 @@ -107,8 +107,20 @@ public class CodePush implements ReactPackage { } currentInstance = this; + + clearDebugCacheIfNeeded(); } + private void clearDebugCacheIfNeeded() { + if (isDebugMode && isPendingUpdate(null)) { + // 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 + File cachedDevBundle = new File(applicationContext.getFilesDir(), "ReactNativeDevBundle.js"); + if (cachedDevBundle.exists()) { + cachedDevBundle.delete(); + } + } + } + private long getBinaryResourcesModifiedTime() { ZipFile applicationFile = null; try { @@ -374,14 +386,6 @@ public class CodePush implements ReactPackage { public void initialize() { CodePush.this.initializeUpdateAfterRestart(); } - - private void clearReactDevBundleCache() { - // 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 - File cachedDevBundle = new File(CodePush.this.applicationContext.getFilesDir(), "ReactNativeDevBundle.js"); - if (cachedDevBundle.exists()) { - cachedDevBundle.delete(); - } - } private void loadBundleLegacy() { Intent intent = mainActivity.getIntent(); @@ -392,10 +396,7 @@ public class CodePush implements ReactPackage { } private void loadBundle() { - // Clear the React dev bundle cache so that new updates can be loaded. - if (CodePush.this.isDebugMode) { - clearReactDevBundleCache(); - } + CodePush.this.clearDebugCacheIfNeeded(); try { // #1) Get the private ReactInstanceManager, which is what includes From 9085a615b7d7c34644bcea8c4828faccd8e418a9 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Tue, 19 Apr 2016 11:51:13 -0700 Subject: [PATCH 22/53] Adding getCurrentPackage docs back --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 5e0658a..9cbb7ff 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,8 @@ When you require `react-native-code-push`, the module object provides the follow * [checkForUpdate](#codepushcheckforupdate): Asks the CodePush service whether the configured app deployment has an update available. +* [getCurrentPackage](#codepushgetcurrentpackage): Retrieves the metadata about the currently installed update (e.g. description, installation time, size). *NOTE: As of `v1.10.3-beta` of the CodePush module, this method is deprecated in favor of [`getUpdateMetadata`](#codepushgetupdatemetadata)*. + * [getUpdateMetadata](#codepushgetupdatemetadata): Retrieves the metadata for an installed update (e.g. description, mandatory). * [notifyApplicationReady](#codepushnotifyapplicationready): Notifies the CodePush runtime that an installed update is considered successful. If you are manually checking for and installing updates (i.e. not using the [sync](#codepushsync) method to handle it all for you), then this method **MUST** be called; otherwise CodePush will treat the update as failed and rollback to the previous version when the app next restarts. @@ -417,6 +419,39 @@ codePush.checkForUpdate() }); ``` +#### codePush.getCurrentPackage + +*NOTE: This method is considered deprecated as of `v1.10.3-beta` of the CodePush module. If you're running this version (or newer), we would recommend using the [`codePush.getUpdateMetadata`](#codepushgetupdatemetadata) instead, since it has more predictable behavior.* + +```javascript +codePush.getCurrentPackage(): Promise; +``` + +Retrieves the metadata about the currently installed "package" (e.g. description, installation time). This can be useful for scenarios such as displaying a "what's new?" dialog after an update has been applied or checking whether there is a pending update that is waiting to be applied via a resume or restart. + +This method returns a `Promise` which resolves to one of two possible values: + +1. `null` if the app is currently running the JS bundle from the binary and not a CodePush update. This occurs in the following scenarios: + + 1. The end-user installed the app binary and has yet to install a CodePush update + 1. The end-user installed an update of the binary (e.g. from the store), which cleared away the old CodePush updates, and gave precedence back to the JS binary in the binary. + +2. A [`LocalPackage`](#localpackage) instance which represents the metadata for the currently running CodePush update. + +Example Usage: + +```javascript +codePush.getCurrentPackage() +.then((update) => { + // If the current app "session" represents the first time + // this update has run, and it had a description provided + // with it upon release, let's show it to the end user + if (update.isFirstRun && update.description) { + // Display a "what's new?" modal + } +}); +``` + #### codePush.getUpdateMetadata ```javascript From b0b958013333b46b8003ba838b04e371d0632bf2 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Tue, 19 Apr 2016 15:31:18 -0700 Subject: [PATCH 23/53] clear updates on init --- ios/CodePush/CodePush.m | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 7312d56..aa39052 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -247,6 +247,20 @@ static NSString *bundleResourceName = @"main"; */ - (void)initializeUpdateAfterRestart { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([_bridge.bundleURL.scheme hasPrefix:@"http"]) { + NSError *error; + NSString *binaryAppVersion = [[CodePushConfig current] appVersion]; + NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; + if (currentPackageMetadata) { + NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey]; + if (![binaryAppVersion isEqualToString:packageAppVersion]) { + [CodePush clearUpdates]; + } + } + } + }); + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { From 96c629d0a1f11c48f1e8d957bc6e7cf5b3e53727 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Apr 2016 00:32:41 -0700 Subject: [PATCH 24/53] extract clearDebugUpdates to seperate function --- ios/CodePush/CodePush.m | 105 +++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index aa39052..3730d41 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -76,21 +76,21 @@ static NSString *bundleResourceName = @"main"; { bundleResourceName = resourceName; bundleResourceExtension = resourceExtension; - + [self ensureBinaryBundleExists]; - + NSString *logMessageFormat = @"Loading JS bundle from %@"; - + NSError *error; NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error]; NSURL *binaryBundleURL = [self binaryBundleURL]; - + if (error || !packageFile) { NSLog(logMessageFormat, binaryBundleURL); isRunningBinaryVersion = YES; return binaryBundleURL; } - + NSString *binaryAppVersion = [[CodePushConfig current] appVersion]; NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; if (error || !currentPackageMetadata) { @@ -98,10 +98,10 @@ static NSString *bundleResourceName = @"main"; isRunningBinaryVersion = YES; return binaryBundleURL; } - + NSString *packageDate = [currentPackageMetadata objectForKey:BinaryBundleDateKey]; NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey]; - + if ([[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL] isEqualToString:packageDate] && ([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]; @@ -113,11 +113,11 @@ static NSString *bundleResourceName = @"main"; #ifndef DEBUG isRelease = YES; #endif - + if (isRelease || ![binaryAppVersion isEqualToString:packageAppVersion]) { [CodePush clearUpdates]; } - + NSLog(logMessageFormat, binaryBundleURL); isRunningBinaryVersion = YES; return binaryBundleURL; @@ -186,7 +186,7 @@ static NSString *bundleResourceName = @"main"; @"codePushInstallModeOnNextRestart":@(CodePushInstallModeOnNextRestart), @"codePushInstallModeImmediate": @(CodePushInstallModeImmediate), @"codePushInstallModeOnNextResume": @(CodePushInstallModeOnNextResume), - + @"codePushUpdateStateRunning": @(CodePushUpdateStateRunning), @"codePushUpdateStatePending": @(CodePushUpdateStatePending), @"codePushUpdateStateLatest": @(CodePushUpdateStateLatest) @@ -208,23 +208,23 @@ static NSString *bundleResourceName = @"main"; { if (![self binaryBundleURL]) { NSString *errorMessage; - + #if TARGET_IPHONE_SIMULATOR errorMessage = @"React Native doesn't generate your app's JS bundle by default when deploying to the simulator. " "If you'd like to test CodePush using the simulator, you can do one of three things depending on your React " "Native version and/or preferred workflow:\n\n" - + "1. Update your AppDelegate.m file to load the JS bundle from the packager instead of from CodePush. " "You can still test your CodePush update experience using this workflow (debug builds only).\n\n" - + "2. Force the JS bundle to be generated in simulator builds by removing the if block that echoes " "\"Skipping bundling for Simulator platform\" in the \"node_modules/react-native/packager/react-native-xcode.sh\" file.\n\n" - + "3. Deploy a release build to the simulator, which unlike debug builds, will generate the JS bundle (React Native >=0.22.0 only)."; #else errorMessage = [NSString stringWithFormat:@"The specified JS bundle file wasn't found within the app's binary. Is \"%@\" the correct file name?", [bundleResourceName stringByAppendingPathExtension:bundleResourceExtension]]; #endif - + RCTFatal([CodePushErrorUtils errorWithMessage:errorMessage]); } } @@ -232,20 +232,15 @@ static NSString *bundleResourceName = @"main"; - (instancetype)init { self = [super init]; - + if (self) { [self initializeUpdateAfterRestart]; } - + return self; } -/* - * This method is used when the app is started to either - * initialize a pending update or rollback a faulty update - * to the previous version. - */ -- (void)initializeUpdateAfterRestart +- (void)clearDebugUpdates { dispatch_async(dispatch_get_main_queue(), ^{ if ([_bridge.bundleURL.scheme hasPrefix:@"http"]) { @@ -260,6 +255,16 @@ static NSString *bundleResourceName = @"main"; } } }); +} + +/* + * This method is used when the app is started to either + * initialize a pending update or rollback a faulty update + * to the previous version. + */ +- (void)initializeUpdateAfterRestart +{ + [self clearDebugUpdates]; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; @@ -305,7 +310,7 @@ static NSString *bundleResourceName = @"main"; } } } - + return NO; } } @@ -319,13 +324,13 @@ static NSString *bundleResourceName = @"main"; { NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; - + // If there is a pending update whose "state" isn't loading, then we consider it "pending". // Additionally, if a specific hash was provided, we ensure it matches that of the pending update. BOOL updateIsPending = pendingUpdate && [pendingUpdate[PendingUpdateIsLoadingKey] boolValue] == NO && (!packageHash || [pendingUpdate[PendingUpdateHashKey] isEqualToString:packageHash]); - + return updateIsPending; } @@ -346,7 +351,7 @@ static NSString *bundleResourceName = @"main"; if ([CodePush isUsingTestConfiguration] || ![_bridge.bundleURL.scheme hasPrefix:@"http"]) { [_bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"]; } - + [_bridge reload]; }); } @@ -362,10 +367,10 @@ static NSString *bundleResourceName = @"main"; { NSError *error; NSDictionary *failedPackage = [CodePushPackage getCurrentPackage:&error]; - + // 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]; [CodePush removePendingUpdate]; @@ -388,7 +393,7 @@ static NSString *bundleResourceName = @"main"; // objects, regardless if you stored something mutable. failedUpdates = [failedUpdates mutableCopy]; } - + [failedUpdates addObject:failedPackage]; [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; [preferences synchronize]; @@ -430,7 +435,7 @@ static NSString *bundleResourceName = @"main"; NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys: packageHash,PendingUpdateHashKey, [NSNumber numberWithBool:isLoading],PendingUpdateIsLoadingKey, nil]; - + [preferences setObject:pendingUpdate forKey:PendingUpdateKey]; [preferences synchronize]; } @@ -471,7 +476,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [mutableUpdatePackage setValue:[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL] forKey:BinaryBundleDateKey]; } - + [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] @@ -492,11 +497,11 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage dispatch_async(_methodQueue, ^{ NSError *err; NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err]; - + if (err) { return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } - + resolve(newPackage); }); } @@ -506,7 +511,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if ([CodePushErrorUtils isCodePushError:err]) { [self saveFailedUpdate:mutableUpdatePackage]; } - + reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }); }]; @@ -531,7 +536,7 @@ RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve resolve(configuration); return; } - + if (binaryHash == nil) { // The hash was not generated either due to a previous unknown error or the fact that // the React Native assets were not bundled in the binary (e.g. during dev/simulator) @@ -539,13 +544,13 @@ RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve resolve(configuration); return; } - + NSMutableDictionary *mutableConfiguration = [configuration mutableCopy]; [mutableConfiguration setObject:binaryHash forKey:PackageHashKey]; resolve(mutableConfiguration); return; } - + resolve(configuration); } @@ -567,10 +572,10 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState // wanted to retrieve the pending or running update. return resolve(nil); } - + // We have a CodePush update, so let's see if it's currently in a pending state. BOOL currentUpdateIsPending = [self isPendingUpdate:[package objectForKey:PackageHashKey]]; - + if (updateState == CodePushUpdateStatePending && !currentUpdateIsPending) { // The caller wanted a pending update // but there isn't currently one. @@ -590,7 +595,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(CodePushUpdateState)updateState // disk that is not actually running. [package setObject:@(YES) forKey:@"_isDebugOnly"]; } - + // Enable differentiating pending vs. non-pending updates [package setObject:@(currentUpdateIsPending) forKey:PackageIsPendingKey]; resolve(package); @@ -610,16 +615,16 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage [CodePushPackage installPackage:updatePackage removePendingUpdate:[self isPendingUpdate:nil] error:&error]; - + if (error) { reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); } else { [self savePendingUpdate:updatePackage[PackageHashKey] isLoading:NO]; - + if (installMode == CodePushInstallModeOnNextResume) { _minimumBackgroundDuration = minimumBackgroundDuration; - + if (!_hasResumeListener) { // Ensure we do not add the listener twice. // Register for app resume notifications so that we @@ -628,16 +633,16 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage selector:@selector(applicationWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:[UIApplication sharedApplication]]; - + _hasResumeListener = YES; } } - + // Signal to JS that the update has been applied. resolve(nil); } @@ -668,7 +673,7 @@ RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash && nil != packageHash && [packageHash length] > 0 && [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]]; - + resolve(@(isFirstRun)); } @@ -715,7 +720,7 @@ RCT_EXPORT_METHOD(downloadAndReplaceCurrentBundle:(NSString *)remoteBundleUrl) */ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ +{ if (needToReportRollback) { needToReportRollback = NO; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; @@ -739,7 +744,7 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve resolve([CodePushTelemetryManager getBinaryUpdateReport:appVersion]); return; } - + resolve(nil); } From 7a3ba2efa4fa6406fa06667464ea6bda396bca0f Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Apr 2016 09:53:12 -0700 Subject: [PATCH 25/53] CR feedback --- ios/CodePush/CodePush.m | 42 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 3730d41..d8eb862 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -172,6 +172,29 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; +/* + * This method is used to clear updates that are installed + * under a different app version and hence don't apply anymore, + * during a debug run configuration and when the bridge is + * running the JS bundle from the dev server. + */ +- (void)clearDebugUpdates +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if ([_bridge.bundleURL.scheme hasPrefix:@"http"]) { + NSError *error; + NSString *binaryAppVersion = [[CodePushConfig current] appVersion]; + NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; + if (currentPackageMetadata) { + NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey]; + if (![binaryAppVersion isEqualToString:packageAppVersion]) { + [CodePush clearUpdates]; + } + } + } + }); +} + /* * This method is used by the React Native bridge to allow * our plugin to expose constants to the JS-side. In our case @@ -240,23 +263,6 @@ static NSString *bundleResourceName = @"main"; return self; } -- (void)clearDebugUpdates -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if ([_bridge.bundleURL.scheme hasPrefix:@"http"]) { - NSError *error; - NSString *binaryAppVersion = [[CodePushConfig current] appVersion]; - NSDictionary *currentPackageMetadata = [CodePushPackage getCurrentPackage:&error]; - if (currentPackageMetadata) { - NSString *packageAppVersion = [currentPackageMetadata objectForKey:AppVersionKey]; - if (![binaryAppVersion isEqualToString:packageAppVersion]) { - [CodePush clearUpdates]; - } - } - } - }); -} - /* * This method is used when the app is started to either * initialize a pending update or rollback a faulty update @@ -264,7 +270,9 @@ static NSString *bundleResourceName = @"main"; */ - (void)initializeUpdateAfterRestart { +#ifdef DEBUG [self clearDebugUpdates]; +#endif NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; From ab059c34a268ae9b2315020e841373ded3e41d89 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 20 Apr 2016 09:55:53 -0700 Subject: [PATCH 26/53] bump version --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f978e84..c32a991 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-code-push", - "version": "1.10.2-beta", + "version": "1.10.3-beta", "description": "React Native plugin for the CodePush service", "main": "CodePush.js", "homepage": "https://microsoft.github.io/code-push", @@ -23,12 +23,12 @@ "packageInstance": "new CodePush(${androidDeploymentKey}, this, BuildConfig.DEBUG)" }, "ios": { - "sharedLibraries": ["libz"] + "sharedLibraries": ["libz"] }, "params": [{ "type": "input", "name": "androidDeploymentKey", - "message": "What is your CodePush deployment key for Android (hit to ignore)" + "message": "What is your CodePush deployment key for Android (hit to ignore)" }] } } \ No newline at end of file From 08a1ad5a2dee72451683cb652d47f23ef87b883f Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 20 Apr 2016 15:10:01 -0700 Subject: [PATCH 27/53] Add download progress sample --- README.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9cbb7ff..36b4b73 100644 --- a/README.md +++ b/README.md @@ -617,24 +617,31 @@ In addition to the options, the `sync` method also accepts two optional function * __syncStatusChangedCallback__ *((syncStatus: Number) => void)* - Called when the sync process moves from one stage to another in the overall update process. The method is called with a status code which represents the current state, and can be any of the [`SyncStatus`](#syncstatus) values. * __downloadProgressCallback__ *((progress: DownloadProgress) => void)* - Called periodically when an available update is being downloaded from the CodePush server. The method is called with a `DownloadProgress` object, which contains the following two properties: - * __totalBytes__ *(Number)* - The total number of bytes expected to be received for this update package - * __receivedBytes__ *(Number)* - The number of bytes downloaded thus far. + + * __totalBytes__ *(Number)* - The total number of bytes expected to be received for this update (i.e. the size of the set of files which changed from the previous release). + + * __receivedBytes__ *(Number)* - The number of bytes downloaded thus far, which can be used to track download progress. Example Usage: ```javascript // Prompt the user when an update is available // and then display a "downloading" modal -codePush.sync({ updateDialog: true }, (status) => { - switch (status) { - case codePush.SyncStatus.DOWNLOADING_PACKAGE: - // Show "downloading" modal - break; - case codePush.SyncStatus.INSTALLING_UPDATE: - // Hide "downloading" modal - break; - } -}); +codePush.sync({ updateDialog: true }, + (status) => { + switch (status) { + case codePush.SyncStatus.DOWNLOADING_PACKAGE: + // Show "downloading" modal + break; + case codePush.SyncStatus.INSTALLING_UPDATE: + // Hide "downloading" modal + break; + } + }, + (totalBytes, receivedBytes) => { + /* Update download modal progress */ + } +); ``` This method returns a `Promise` which is resolved to a `SyncStatus` code that indicates why the `sync` call succeeded. This code can be one of the following `SyncStatus` values: From 25f313a7ed2e73e6e38d4bd27a302a1a7cfe71f5 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 20 Apr 2016 15:15:03 -0700 Subject: [PATCH 28/53] Fix notifyAppReady link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36b4b73..8d846df 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,7 @@ When you require `react-native-code-push`, the module object provides the follow * [getUpdateMetadata](#codepushgetupdatemetadata): Retrieves the metadata for an installed update (e.g. description, mandatory). -* [notifyApplicationReady](#codepushnotifyapplicationready): Notifies the CodePush runtime that an installed update is considered successful. If you are manually checking for and installing updates (i.e. not using the [sync](#codepushsync) method to handle it all for you), then this method **MUST** be called; otherwise CodePush will treat the update as failed and rollback to the previous version when the app next restarts. +* [notifyAppReady](#codepushnotifyappready): Notifies the CodePush runtime that an installed update is considered successful. If you are manually checking for and installing updates (i.e. not using the [sync](#codepushsync) method to handle it all for you), then this method **MUST** be called; otherwise CodePush will treat the update as failed and rollback to the previous version when the app next restarts. * [restartApp](#codepushrestartapp): Immediately restarts the app. If there is an update pending, it will be immediately displayed to the end user. Otherwise, calling this method simply has the same behavior as the end user killing and restarting the process. From 4dc2e00908d26dfe099e15abe4d6f76535667db2 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 22 Apr 2016 15:33:35 -0700 Subject: [PATCH 29/53] fix #305 --- .../microsoft/codepush/react/CodePush.java | 56 ++++++++++--------- ios/CodePush/CodePush.m | 10 +++- 2 files changed, 37 insertions(+), 29 deletions(-) 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 5722ee5..2b23fec 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 @@ -107,10 +107,10 @@ public class CodePush implements ReactPackage { } currentInstance = this; - + clearDebugCacheIfNeeded(); } - + private void clearDebugCacheIfNeeded() { if (isDebugMode && isPendingUpdate(null)) { // 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 @@ -120,7 +120,7 @@ public class CodePush implements ReactPackage { } } } - + private long getBinaryResourcesModifiedTime() { ZipFile applicationFile = null; try { @@ -144,7 +144,7 @@ public class CodePush implements ReactPackage { public static String getBundleUrl() { return getBundleUrl(DEFAULT_JS_BUNDLE_NAME); } - + public static String getBundleUrl(String assetsBundleFileName) { if (currentInstance == null) { throw new CodePushNotInitializedException("A CodePush instance has not been created yet. Have you added it to your app's list of ReactPackages?"); @@ -152,7 +152,7 @@ public class CodePush implements ReactPackage { return currentInstance.getBundleUrlInternal(assetsBundleFileName); } - + public String getBundleUrlInternal(String assetsBundleFileName) { this.assetsBundleFileName = assetsBundleFileName; String binaryJsBundleUrl = ASSETS_BUNDLE_PREFIX + assetsBundleFileName; @@ -230,7 +230,7 @@ public class CodePush implements ReactPackage { return null; } } - + private void initializeUpdateAfterRestart() { JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { @@ -277,7 +277,7 @@ public class CodePush implements ReactPackage { private boolean isPendingUpdate(String packageHash) { JSONObject pendingUpdate = getPendingUpdate(); - + try { return pendingUpdate != null && !pendingUpdate.getBoolean(PENDING_UPDATE_IS_LOADING_KEY) && @@ -297,7 +297,7 @@ public class CodePush implements ReactPackage { SharedPreferences settings = applicationContext.getSharedPreferences(CODE_PUSH_PREFERENCES, 0); settings.edit().remove(PENDING_UPDATE_KEY).commit(); } - + private void rollbackPackage() { WritableMap failedPackage = codePushPackage.getCurrentPackage(); saveFailedUpdate(failedPackage); @@ -357,23 +357,23 @@ public class CodePush implements ReactPackage { private class CodePushNativeModule extends ReactContextBaseJavaModule { private LifecycleEventListener lifecycleEventListener = null; private int minimumBackgroundDuration = 0; - + public CodePushNativeModule(ReactApplicationContext reactContext) { super(reactContext); } - + @Override public Map getConstants() { final Map constants = new HashMap<>(); - + constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue()); constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue()); constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue()); - + constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue()); constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue()); constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue()); - + return constants; } @@ -381,23 +381,23 @@ public class CodePush implements ReactPackage { public String getName() { return "CodePush"; } - + @Override public void initialize() { CodePush.this.initializeUpdateAfterRestart(); } - + private void loadBundleLegacy() { Intent intent = mainActivity.getIntent(); mainActivity.finish(); mainActivity.startActivity(intent); - + currentInstance = null; } - + private void loadBundle() { CodePush.this.clearDebugCacheIfNeeded(); - + try { // #1) Get the private ReactInstanceManager, which is what includes // the logic to reload the current React context. @@ -410,7 +410,7 @@ public class CodePush implements ReactPackage { Field jsBundleField = instanceManager.getClass().getDeclaredField("mJSBundleFile"); jsBundleField.setAccessible(true); jsBundleField.set(instanceManager, latestJSBundleFile); - + // #3) Get the context creation method and fire it on the UI thread (which RN enforces) final Method recreateMethod = instanceManager.getClass().getMethod("recreateReactContextInBackground"); mainActivity.runOnUiThread(new Runnable() { @@ -488,14 +488,14 @@ public class CodePush implements ReactPackage { promise.resolve(configMap); } - + @ReactMethod public void getUpdateMetadata(final int updateState, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { WritableMap currentPackage = codePushPackage.getCurrentPackage(); - + if (currentPackage == null) { promise.resolve(""); return null; @@ -507,7 +507,7 @@ public class CodePush implements ReactPackage { String currentHash = currentPackage.getString(PACKAGE_HASH_KEY); currentUpdateIsPending = CodePush.this.isPendingUpdate(currentHash); } - + if (updateState == CodePushUpdateState.PENDING.getValue() && !currentUpdateIsPending) { // The caller wanted a pending update // but there isn't currently one. @@ -532,7 +532,7 @@ public class CodePush implements ReactPackage { currentPackage.putBoolean("isPending", currentUpdateIsPending); promise.resolve(currentPackage); } - + return null; } }; @@ -615,7 +615,11 @@ public class CodePush implements ReactPackage { public void onHostResume() { // Determine how long the app was in the background and ensure // that it meets the minimum duration amount of time. - long durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000; + long durationInBackground = 0; + if (lastPausedDate != null) { + durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000; + } + if (durationInBackground >= CodePushNativeModule.this.minimumBackgroundDuration) { loadBundle(); } @@ -645,7 +649,7 @@ public class CodePush implements ReactPackage { asyncTask.execute(); } - + @ReactMethod public void isFailedUpdate(String packageHash, Promise promise) { promise.resolve(isFailedHash(packageHash)); @@ -665,7 +669,7 @@ public class CodePush implements ReactPackage { removePendingUpdate(); promise.resolve(""); } - + @ReactMethod public void restartApp(boolean onlyIfUpdateIsPending) { // If this is an unconditional restart request, or there diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index d8eb862..689f943 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -175,7 +175,7 @@ static NSString *bundleResourceName = @"main"; /* * This method is used to clear updates that are installed * under a different app version and hence don't apply anymore, - * during a debug run configuration and when the bridge is + * during a debug run configuration and when the bridge is * running the JS bundle from the dev server. */ - (void)clearDebugUpdates @@ -273,7 +273,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -456,7 +456,11 @@ static NSString *bundleResourceName = @"main"; { // Determine how long the app was in the background and ensure // that it meets the minimum duration amount of time. - int durationInBackground = [[NSDate date] timeIntervalSinceDate:_lastResignedDate]; + int durationInBackground = 0; + if (_lastResignedDate) { + durationInBackground = [[NSDate date] timeIntervalSinceDate:_lastResignedDate]; + } + if (durationInBackground >= _minimumBackgroundDuration) { [self loadBundle]; } From 3e29100c6cd554e20d869f38a1ec51351fb7c90e Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Fri, 22 Apr 2016 15:43:51 -0700 Subject: [PATCH 30/53] bump package.json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c32a991..22ee925 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-code-push", - "version": "1.10.3-beta", + "version": "1.10.4-beta", "description": "React Native plugin for the CodePush service", "main": "CodePush.js", "homepage": "https://microsoft.github.io/code-push", From 584445de31244b02f7eebb1de9facb1d830177e2 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Fri, 22 Apr 2016 16:00:19 -0700 Subject: [PATCH 31/53] Bumping version --- CodePush.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodePush.podspec b/CodePush.podspec index ff149f9..234e44f 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'CodePush' - s.version = '1.10.2' + s.version = '1.10.4' s.summary = 'React Native plugin for the CodePush service' s.author = 'Microsoft Corporation' s.license = 'MIT' From 2c7fee3a7986819e61c6c9be75cd0ba3dc4707d6 Mon Sep 17 00:00:00 2001 From: Johan Ruokangas Date: Sun, 24 Apr 2016 12:41:07 +0300 Subject: [PATCH 32/53] Fix roll back link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d846df..45948c6 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ A React Native app is composed of JavaScript files and any accompanying [images] The CodePush plugin helps get product improvements in front of your end users instantly, by keeping your JavaScript and images synchronized with updates you release to the CodePush server. This way, your app gets the benefits of an offline mobile experience, as well as the "web-like" agility of side-loading updates as soon as they are available. It's a win-win! -In order to ensure that your end users always have a functioning version of your app, the CodePush plugin maintains a copy of the previous update, so that in the event that you accidentally push an update which includes a crash, it can automatically roll back. This way, you can rest assured that your newfound release agility won't result in users becoming blocked before you have a chance to [roll back](http://microsoft.github.io/code-push/docs/cli.html#link-8) on the server. It's a win-win-win! +In order to ensure that your end users always have a functioning version of your app, the CodePush plugin maintains a copy of the previous update, so that in the event that you accidentally push an update which includes a crash, it can automatically roll back. This way, you can rest assured that your newfound release agility won't result in users becoming blocked before you have a chance to [roll back](http://microsoft.github.io/code-push/docs/cli.html#link-10) on the server. It's a win-win-win! *Note: Any product changes which touch native code (e.g. modifying your `AppDelegate.m`/`MainActivity.java` file, adding a new plugin) cannot be distributed via CodePush, and therefore, must be updated via the appropriate store(s).* From 8c05c833de8b895db8f4b01441e318512f95dbbd Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 24 Apr 2016 14:09:40 -0700 Subject: [PATCH 33/53] Updating supported components list --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 45948c6..612cc94 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,12 @@ We try our best to maintain backwards compatability of our plugin with previous ## Supported Components -When using the React Native assets sytem (i.e. using the `require("./foo.png")` syntax), the following list represents the set of components (and props) that support having their referenced images updated via CodePush: +When using the React Native assets sytem (i.e. using the `require("./foo.png")` syntax), the following list represents the set of core components (and props) that support having their referenced images updated via CodePush: | Component | Prop(s) | |-------------------------------------------------|------------------------------------------| -| `Image` | `source` | +| `Image` | `source` | +| `MapView.Marker`
*(Requires [react-native-maps](https://github.com/lelandrichardson/react-native-maps) `>=O.3.2`)* | `image` | | `ProgressViewIOS` | `progressImage`, `trackImage` | | `TabBarIOS.Item` | `icon`, `selectedIcon` | | `ToolbarAndroid`
*(React Native 0.21.0+)* | `actions[].icon`, `logo`, `overflowIcon` | @@ -67,6 +68,7 @@ The following list represents the set of components (and props) that don't curre | Component | Prop(s) | |-------------|----------------------------------------------------------------------| | `SliderIOS` | `maximumTrackImage`, `minimumTrackImage`, `thumbImage`, `trackImage` | +| `Video` | `source` | As new core components are released, which support referencing assets, we'll update this list to ensure users know what exactly they can expect to update using CodePush. From dc1661e063f891e71ffbac2da012b976cddefed8 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 24 Apr 2016 22:12:57 -0700 Subject: [PATCH 34/53] Syncing version with package.json --- CodePush.podspec | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CodePush.podspec b/CodePush.podspec index 234e44f..daf11d3 100644 --- a/CodePush.podspec +++ b/CodePush.podspec @@ -1,8 +1,12 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + Pod::Spec.new do |s| s.name = 'CodePush' - s.version = '1.10.4' - s.summary = 'React Native plugin for the CodePush service' + s.version = package['version'].sub('-beta', '') + s.summary = 'React Native module for the CodePush service' s.author = 'Microsoft Corporation' s.license = 'MIT' s.homepage = 'http://microsoft.github.io/code-push/' @@ -14,4 +18,4 @@ Pod::Spec.new do |s| s.library = 'z' s.dependency 'React' -end +end \ No newline at end of file From 7916856aa4fac5df97b760ff0f0489e4a8fa394d Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Mon, 25 Apr 2016 15:04:19 -0700 Subject: [PATCH 35/53] fix rollback android --- Examples/CodePushDemoApp/crossplatformdemo.js | 20 +++++------ .../microsoft/codepush/react/CodePush.java | 36 ++++++++++--------- .../codepush/react/CodePushDialog.java | 2 -- .../react/CodePushMalformedDataException.java | 12 ------- .../codepush/react/CodePushPackage.java | 26 +++++++------- .../react/CodePushUnknownException.java | 12 ------- .../codepush/react/CodePushUpdateUtils.java | 4 +-- .../codepush/react/CodePushUtils.java | 23 +++++++----- .../microsoft/codepush/react/FileUtils.java | 8 ++--- ios/CodePush/CodePushConfig.m | 10 +++--- request-fetch-adapter.js | 2 +- 11 files changed, 70 insertions(+), 85 deletions(-) delete mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java delete mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java diff --git a/Examples/CodePushDemoApp/crossplatformdemo.js b/Examples/CodePushDemoApp/crossplatformdemo.js index d941af0..7111d1c 100644 --- a/Examples/CodePushDemoApp/crossplatformdemo.js +++ b/Examples/CodePushDemoApp/crossplatformdemo.js @@ -18,13 +18,13 @@ let CodePushDemoApp = React.createClass({ let self = this; try { return await CodePush.sync( - { + { updateDialog: true, installMode: CodePush.InstallMode.ON_NEXT_RESUME - }, + }, (syncStatus) => { switch(syncStatus) { - case CodePush.SyncStatus.CHECKING_FOR_UPDATE: + case CodePush.SyncStatus.CHECKING_FOR_UPDATE: self.setState({ syncMessage: "Checking for update." }); @@ -80,36 +80,36 @@ let CodePushDemoApp = React.createClass({ CodePush.log(error); } }, - + componentDidMount() { CodePush.notifyApplicationReady(); }, - + getInitialState() { return { }; }, - + render() { let syncView, syncButton, progressView; - + if (this.state.syncMessage) { syncView = ( {this.state.syncMessage} ); } else { - syncButton = ( + syncButton = ( ); } - + if (this.state.progress) { progressView = ( {this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received ); } - + 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 2b23fec..551d0c4 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 @@ -99,7 +99,7 @@ public class CodePush implements ReactPackage { appVersion = pInfo.versionName; buildVersion = pInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { - throw new CodePushUnknownException("Unable to get package info for " + applicationContext.getPackageName(), e); + CodePushUtils.logException("Unable to get package info for " + applicationContext.getPackageName(), e); } if (currentInstance != null) { @@ -109,6 +109,7 @@ public class CodePush implements ReactPackage { currentInstance = this; clearDebugCacheIfNeeded(); + initializeUpdateAfterRestart(); } private void clearDebugCacheIfNeeded() { @@ -129,13 +130,14 @@ public class CodePush implements ReactPackage { ZipEntry classesDexEntry = applicationFile.getEntry(RESOURCES_BUNDLE); return classesDexEntry.getTime(); } catch (PackageManager.NameNotFoundException | IOException e) { - throw new CodePushUnknownException("Error in getting file information about compiled resources", e); + CodePushUtils.logException("Error in getting file information about compiled resources", e); + return -1; } finally { if (applicationFile != null) { try { applicationFile.close(); } catch (IOException e) { - throw new CodePushUnknownException("Error in closing application file.", e); + CodePushUtils.logException("Error in closing application file.", e); } } } @@ -193,7 +195,8 @@ public class CodePush implements ReactPackage { return binaryJsBundleUrl; } } catch (NumberFormatException e) { - throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e); + CodePushUtils.logException("Error in closing application file.", e); + return binaryJsBundleUrl; } } @@ -251,7 +254,7 @@ public class CodePush implements ReactPackage { } } catch (JSONException e) { // Should not happen. - throw new CodePushUnknownException("Unable to read pending update metadata stored in SharedPreferences", e); + CodePushUtils.logException("Unable to read pending update metadata stored in SharedPreferences", e); } } } @@ -267,7 +270,7 @@ public class CodePush implements ReactPackage { return true; } } catch (JSONException e) { - throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e); + CodePushUtils.logException("Unable to read failedUpdates data stored in SharedPreferences.", e); } } } @@ -284,7 +287,8 @@ public class CodePush implements ReactPackage { (packageHash == null || pendingUpdate.getString(PENDING_UPDATE_HASH_KEY).equals(packageHash)); } catch (JSONException e) { - throw new CodePushUnknownException("Unable to read pending update metadata in isPendingUpdate.", e); + CodePushUtils.logException("Unable to read pending update metadata in isPendingUpdate.", e); + return false; } } @@ -316,8 +320,9 @@ public class CodePush implements ReactPackage { failedUpdates = new JSONArray(failedUpdatesString); } catch (JSONException e) { // Should not happen. - throw new CodePushMalformedDataException("Unable to parse failed updates information " + + CodePushUtils.logException("Unable to parse failed updates information " + failedUpdatesString + " stored in SharedPreferences", e); + failedUpdates = new JSONArray(); } } @@ -335,7 +340,7 @@ public class CodePush implements ReactPackage { settings.edit().putString(PENDING_UPDATE_KEY, pendingUpdate.toString()).commit(); } catch (JSONException e) { // Should not happen. - throw new CodePushUnknownException("Unable to save pending update.", e); + CodePushUtils.logException("Unable to save pending update.", e); } } @@ -382,11 +387,6 @@ public class CodePush implements ReactPackage { return "CodePush"; } - @Override - public void initialize() { - CodePush.this.initializeUpdateAfterRestart(); - } - private void loadBundleLegacy() { Intent intent = mainActivity.getIntent(); mainActivity.finish(); @@ -418,6 +418,7 @@ public class CodePush implements ReactPackage { public void run() { try { recreateMethod.invoke(instanceManager); + initializeUpdateAfterRestart(); } catch (Exception e) { // The recreation method threw an unknown exception @@ -558,7 +559,7 @@ public class CodePush implements ReactPackage { return null; } } catch (JSONException e) { - throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); + CodePushUtils.logException("Unable to read failed updates information stored in SharedPreferences.", e); } } } else if (didUpdate) { @@ -595,7 +596,8 @@ public class CodePush implements ReactPackage { String pendingHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY); if (pendingHash == null) { - throw new CodePushUnknownException("Update package to be installed has no hash."); + CodePushUtils.log("Update package to be installed has no hash."); + return null; } else { savePendingUpdate(pendingHash, /* isLoading */false); } @@ -687,7 +689,7 @@ public class CodePush implements ReactPackage { try { codePushPackage.downloadAndReplaceCurrentBundle(remoteBundleUrl, assetsBundleFileName); } catch (IOException e) { - throw new CodePushUnknownException("Unable to replace current bundle", e); + CodePushUtils.logException("Unable to replace current bundle", e); } } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java index e8d19fb..a4d8fa3 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java @@ -36,8 +36,6 @@ public class CodePushDialog extends ReactContextBaseJavaModule{ case DialogInterface.BUTTON_NEGATIVE: successCallback.invoke(1); break; - default: - throw new CodePushUnknownException("Unknown button ID pressed."); } } }; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java deleted file mode 100644 index 604a9d7..0000000 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.microsoft.codepush.react; - -import java.net.MalformedURLException; - -public class CodePushMalformedDataException extends RuntimeException { - public CodePushMalformedDataException(String path, Throwable cause) { - super("Unable to parse contents of " + path + ", the file may be corrupted.", cause); - } - public CodePushMalformedDataException(String url, MalformedURLException cause) { - super("The package has an invalid downloadUrl: " + url, cause); - } -} 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 6fefb5e..9b7d0c7 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 @@ -71,7 +71,9 @@ public class CodePushPackage { try { return CodePushUtils.getWritableMapFromFile(statusFilePath); } catch (IOException e) { - throw new CodePushUnknownException("Error getting current package info" , e); + // Should not happen. + CodePushUtils.logException("Error getting current package info" , e); + return new WritableNativeMap(); } } @@ -79,7 +81,8 @@ public class CodePushPackage { try { CodePushUtils.writeReadableMapToFile(packageInfo, getStatusFilePath()); } catch (IOException e) { - throw new CodePushUnknownException("Error updating current package info" , e); + // Should not happen. + CodePushUtils.logException("Error updating current package info" , e); } } @@ -100,6 +103,10 @@ public class CodePushPackage { } WritableMap currentPackage = getCurrentPackage(); + if (currentPackage == null) { + return null; + } + String relativeBundlePath = CodePushUtils.tryGetString(currentPackage, RELATIVE_BUNDLE_PATH_KEY); if (relativeBundlePath == null) { return CodePushUtils.appendPathComponent(packageFolder, bundleFileName); @@ -205,12 +212,10 @@ public class CodePushPackage { } if (totalBytes != receivedBytes) { - throw new CodePushUnknownException("Received " + receivedBytes + " bytes, expected " + totalBytes); + CodePushUtils.log("Received " + receivedBytes + " bytes, expected " + totalBytes); } isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304; - } catch (MalformedURLException e) { - throw new CodePushMalformedDataException(downloadUrlString, e); } finally { try { if (bout != null) bout.close(); @@ -218,7 +223,7 @@ public class CodePushPackage { if (bin != null) bin.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { - throw new CodePushUnknownException("Error closing IO resources.", e); + CodePushUtils.logException("Error closing IO resources.", e); } } @@ -262,9 +267,8 @@ public class CodePushPackage { try { updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath); } catch (JSONException e) { - throw new CodePushUnknownException("Unable to set key " + - RELATIVE_BUNDLE_PATH_KEY + " to value " + relativeBundlePath + - " in update package.", e); + CodePushUtils.logException("Unable to set key " + RELATIVE_BUNDLE_PATH_KEY + + " to value " + relativeBundlePath + " in update package.", e); } updatePackage = CodePushUtils.convertJsonObjectToWritable(updatePackageJSON); @@ -327,8 +331,6 @@ public class CodePushPackage { while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) { bout.write(data, 0, numBytesRead); } - } catch (MalformedURLException e) { - throw new CodePushMalformedDataException(remoteBundleUrl, e); } finally { try { if (bout != null) bout.close(); @@ -336,7 +338,7 @@ public class CodePushPackage { if (bin != null) bin.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { - throw new CodePushUnknownException("Error closing IO resources.", e); + CodePushUtils.logException("Error closing IO resources.", e); } } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java deleted file mode 100644 index cb99f6c..0000000 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.microsoft.codepush.react; - -class CodePushUnknownException extends RuntimeException { - - public CodePushUnknownException(String message, Throwable cause) { - super(message, cause); - } - - public CodePushUnknownException(String message) { - super(message); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java index ee6d78d..4e9c84c 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java @@ -36,7 +36,7 @@ public class CodePushUpdateUtils { manifest.add(relativePath + ":" + computeHash(new FileInputStream(file))); } catch (FileNotFoundException e) { // Should not happen. - throw new CodePushUnknownException("Unable to compute hash of update contents.", e); + CodePushUtils.logException("Unable to compute hash of update contents.", e); } } } @@ -52,7 +52,7 @@ public class CodePushUpdateUtils { while (digestInputStream.read(byteBuffer) != -1); } catch (NoSuchAlgorithmException | IOException e) { // Should not happen. - throw new CodePushUnknownException("Unable to compute hash of update contents.", e); + CodePushUtils.logException("Unable to compute hash of update contents.", e); } finally { try { if (digestInputStream != null) digestInputStream.close(); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java index 3f64aeb..780f1db 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java @@ -38,7 +38,7 @@ public class CodePushUtils { obj = jsonArr.get(i); } catch (JSONException jsonException) { // Should not happen. - throw new CodePushUnknownException(i + " should be within bounds of array " + jsonArr.toString(), jsonException); + CodePushUtils.logException(i + " should be within bounds of array " + jsonArr.toString(), jsonException); } if (obj instanceof JSONObject) @@ -56,7 +56,7 @@ public class CodePushUtils { else if (obj == null) arr.pushNull(); else - throw new CodePushUnknownException("Unrecognized object: " + obj); + CodePushUtils.log("Unrecognized object: " + obj); } return arr; @@ -72,7 +72,7 @@ public class CodePushUtils { obj = jsonObj.get(key); } catch (JSONException jsonException) { // Should not happen. - throw new CodePushUnknownException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException); + CodePushUtils.logException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException); } if (obj instanceof JSONObject) @@ -90,7 +90,7 @@ public class CodePushUtils { else if (obj == null) map.putNull(key); else - throw new CodePushUnknownException("Unrecognized object: " + obj); + CodePushUtils.log("Unrecognized object: " + obj); } return map; @@ -124,7 +124,7 @@ public class CodePushUtils { try { jsonArr.put(number.doubleValue()); } catch (JSONException jsonException) { - throw new CodePushUnknownException("Unable to put value " + arr.getDouble(i) + " in JSONArray"); + CodePushUtils.log("Unable to put value " + arr.getDouble(i) + " in JSONArray"); } } break; @@ -167,10 +167,10 @@ public class CodePushUtils { jsonObj.put(key, null); break; default: - throw new CodePushUnknownException("Unrecognized type: " + type + " of key: " + key); + CodePushUtils.log("Unrecognized type: " + type + " of key: " + key); } } catch (JSONException jsonException) { - throw new CodePushUnknownException("Error setting key: " + key + " in JSONObject", jsonException); + CodePushUtils.logException("Error setting key: " + key + " in JSONObject", jsonException); } } @@ -202,7 +202,9 @@ public class CodePushUtils { JSONObject json = new JSONObject(content); return convertJsonObjectToWritable(json); } catch (JSONException jsonException) { - throw new CodePushMalformedDataException(filePath, jsonException); + // Should not happen + CodePushUtils.logException(filePath, jsonException); + return null; } } @@ -214,6 +216,11 @@ public class CodePushUtils { log("Loading JS bundle from \"" + path + "\""); } + public static void logException(String message, Exception e) { + log(message); + e.printStackTrace(); + } + public static String tryGetString(ReadableMap map, String key) { try { return map.getString(key); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java index fcc8cf5..fe9767b 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java @@ -47,7 +47,7 @@ public class FileUtils { if (fromBufferedStream != null) fromBufferedStream.close(); if (destStream != null) destStream.close(); } catch (IOException e) { - throw new CodePushUnknownException("Error closing IO resources.", e); + CodePushUtils.logException("Error closing IO resources.", e); } } } @@ -110,8 +110,8 @@ public class FileUtils { File newFilePath = new File(newFolderPath, newFileName); if (!fileToMove.renameTo(newFilePath)) { - throw new CodePushUnknownException("Unable to move file from " + - fileToMove.getAbsolutePath() + " to " + newFilePath.getAbsolutePath() + "."); + CodePushUtils.log("Unable to move file from " + fileToMove.getAbsolutePath() + + " to " + newFilePath.getAbsolutePath() + "."); } } @@ -185,7 +185,7 @@ public class FileUtils { if (bufferedStream != null) bufferedStream.close(); if (fileStream != null) fileStream.close(); } catch (IOException e) { - throw new CodePushUnknownException("Error closing IO resources.", e); + CodePushUtils.logException("Error closing IO resources.", e); } } } diff --git a/ios/CodePush/CodePushConfig.m b/ios/CodePush/CodePushConfig.m index 9ea11ff..b84b757 100644 --- a/ios/CodePush/CodePushConfig.m +++ b/ios/CodePush/CodePushConfig.m @@ -27,12 +27,12 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; { self = [super init]; NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; - + NSString *appVersion = [infoDictionary objectForKey:@"CFBundleShortVersionString"]; NSString *buildVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey]; NSString *deploymentKey = [infoDictionary objectForKey:@"CodePushDeploymentKey"]; NSString *serverURL = [infoDictionary objectForKey:@"CodePushServerURL"]; - + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSString *clientUniqueId = [userDefaults stringForKey:ClientUniqueIDConfigKey]; if (clientUniqueId == nil) { @@ -40,11 +40,11 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; [userDefaults setObject:clientUniqueId forKey:ClientUniqueIDConfigKey]; [userDefaults synchronize]; } - + if (!serverURL) { serverURL = @"https://codepush.azurewebsites.net/"; } - + _configDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys: appVersion,AppVersionConfigKey, buildVersion,BuildVdersionConfigKey, @@ -52,7 +52,7 @@ static NSString * const ServerURLConfigKey = @"serverUrl"; clientUniqueId,ClientUniqueIDConfigKey, deploymentKey,DeploymentKeyConfigKey, nil]; - + return self; } diff --git a/request-fetch-adapter.js b/request-fetch-adapter.js index eb61622..16220db 100644 --- a/request-fetch-adapter.js +++ b/request-fetch-adapter.js @@ -21,7 +21,7 @@ module.exports = { headers: headers, body: requestBody }); - + const statusCode = response.status; const body = await response.text(); callback(null, { statusCode, body }); From 9cb4e54a8114edec05a4ad4825518705f567f642 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Mon, 25 Apr 2016 17:52:07 -0700 Subject: [PATCH 36/53] revert runtime exception changes --- .../microsoft/codepush/react/CodePush.java | 29 ++++++++----------- .../codepush/react/CodePushDialog.java | 2 ++ .../react/CodePushMalformedDataException.java | 12 ++++++++ .../codepush/react/CodePushPackage.java | 20 ++++++++----- .../react/CodePushUnknownException.java | 12 ++++++++ .../codepush/react/CodePushUpdateUtils.java | 4 +-- .../codepush/react/CodePushUtils.java | 22 +++++--------- .../microsoft/codepush/react/FileUtils.java | 8 ++--- 8 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java create mode 100644 android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java 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 551d0c4..c071928 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 @@ -99,7 +99,7 @@ public class CodePush implements ReactPackage { appVersion = pInfo.versionName; buildVersion = pInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { - CodePushUtils.logException("Unable to get package info for " + applicationContext.getPackageName(), e); + throw new CodePushUnknownException("Unable to get package info for " + applicationContext.getPackageName(), e); } if (currentInstance != null) { @@ -130,14 +130,13 @@ public class CodePush implements ReactPackage { ZipEntry classesDexEntry = applicationFile.getEntry(RESOURCES_BUNDLE); return classesDexEntry.getTime(); } catch (PackageManager.NameNotFoundException | IOException e) { - CodePushUtils.logException("Error in getting file information about compiled resources", e); - return -1; + throw new CodePushUnknownException("Error in getting file information about compiled resources", e); } finally { if (applicationFile != null) { try { applicationFile.close(); } catch (IOException e) { - CodePushUtils.logException("Error in closing application file.", e); + throw new CodePushUnknownException("Error in closing application file.", e); } } } @@ -195,8 +194,7 @@ public class CodePush implements ReactPackage { return binaryJsBundleUrl; } } catch (NumberFormatException e) { - CodePushUtils.logException("Error in closing application file.", e); - return binaryJsBundleUrl; + throw new CodePushUnknownException("Error in reading binary modified date from package metadata", e); } } @@ -254,7 +252,7 @@ public class CodePush implements ReactPackage { } } catch (JSONException e) { // Should not happen. - CodePushUtils.logException("Unable to read pending update metadata stored in SharedPreferences", e); + throw new CodePushUnknownException("Unable to read pending update metadata stored in SharedPreferences", e); } } } @@ -270,7 +268,7 @@ public class CodePush implements ReactPackage { return true; } } catch (JSONException e) { - CodePushUtils.logException("Unable to read failedUpdates data stored in SharedPreferences.", e); + throw new CodePushUnknownException("Unable to read failedUpdates data stored in SharedPreferences.", e); } } } @@ -287,8 +285,7 @@ public class CodePush implements ReactPackage { (packageHash == null || pendingUpdate.getString(PENDING_UPDATE_HASH_KEY).equals(packageHash)); } catch (JSONException e) { - CodePushUtils.logException("Unable to read pending update metadata in isPendingUpdate.", e); - return false; + throw new CodePushUnknownException("Unable to read pending update metadata in isPendingUpdate.", e); } } @@ -320,9 +317,8 @@ public class CodePush implements ReactPackage { failedUpdates = new JSONArray(failedUpdatesString); } catch (JSONException e) { // Should not happen. - CodePushUtils.logException("Unable to parse failed updates information " + + throw new CodePushMalformedDataException("Unable to parse failed updates information " + failedUpdatesString + " stored in SharedPreferences", e); - failedUpdates = new JSONArray(); } } @@ -340,7 +336,7 @@ public class CodePush implements ReactPackage { settings.edit().putString(PENDING_UPDATE_KEY, pendingUpdate.toString()).commit(); } catch (JSONException e) { // Should not happen. - CodePushUtils.logException("Unable to save pending update.", e); + throw new CodePushUnknownException("Unable to save pending update.", e); } } @@ -559,7 +555,7 @@ public class CodePush implements ReactPackage { return null; } } catch (JSONException e) { - CodePushUtils.logException("Unable to read failed updates information stored in SharedPreferences.", e); + throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e); } } } else if (didUpdate) { @@ -596,8 +592,7 @@ public class CodePush implements ReactPackage { String pendingHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY); if (pendingHash == null) { - CodePushUtils.log("Update package to be installed has no hash."); - return null; + throw new CodePushUnknownException("Update package to be installed has no hash."); } else { savePendingUpdate(pendingHash, /* isLoading */false); } @@ -689,7 +684,7 @@ public class CodePush implements ReactPackage { try { codePushPackage.downloadAndReplaceCurrentBundle(remoteBundleUrl, assetsBundleFileName); } catch (IOException e) { - CodePushUtils.logException("Unable to replace current bundle", e); + throw new CodePushUnknownException("Unable to replace current bundle", e); } } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java index a4d8fa3..e8d19fb 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java @@ -36,6 +36,8 @@ public class CodePushDialog extends ReactContextBaseJavaModule{ case DialogInterface.BUTTON_NEGATIVE: successCallback.invoke(1); break; + default: + throw new CodePushUnknownException("Unknown button ID pressed."); } } }; diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java new file mode 100644 index 0000000..88b8200 --- /dev/null +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushMalformedDataException.java @@ -0,0 +1,12 @@ +package com.microsoft.codepush.react; + +import java.net.MalformedURLException; + +public class CodePushMalformedDataException extends RuntimeException { + public CodePushMalformedDataException(String path, Throwable cause) { + super("Unable to parse contents of " + path + ", the file may be corrupted.", cause); + } + public CodePushMalformedDataException(String url, MalformedURLException cause) { + super("The package has an invalid downloadUrl: " + url, cause); + } +} \ No newline at end of file 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 9b7d0c7..61cf1ea 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 @@ -72,8 +72,7 @@ public class CodePushPackage { return CodePushUtils.getWritableMapFromFile(statusFilePath); } catch (IOException e) { // Should not happen. - CodePushUtils.logException("Error getting current package info" , e); - return new WritableNativeMap(); + throw new CodePushUnknownException("Error getting current package info" , e); } } @@ -82,7 +81,7 @@ public class CodePushPackage { CodePushUtils.writeReadableMapToFile(packageInfo, getStatusFilePath()); } catch (IOException e) { // Should not happen. - CodePushUtils.logException("Error updating current package info" , e); + throw new CodePushUnknownException("Error updating current package info" , e); } } @@ -212,10 +211,12 @@ public class CodePushPackage { } if (totalBytes != receivedBytes) { - CodePushUtils.log("Received " + receivedBytes + " bytes, expected " + totalBytes); + throw new CodePushUnknownException("Received " + receivedBytes + " bytes, expected " + totalBytes); } isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304; + } catch (MalformedURLException e) { + throw new CodePushMalformedDataException(downloadUrlString, e); } finally { try { if (bout != null) bout.close(); @@ -223,7 +224,7 @@ public class CodePushPackage { if (bin != null) bin.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { - CodePushUtils.logException("Error closing IO resources.", e); + throw new CodePushUnknownException("Error closing IO resources.", e); } } @@ -267,8 +268,9 @@ public class CodePushPackage { try { updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath); } catch (JSONException e) { - CodePushUtils.logException("Unable to set key " + RELATIVE_BUNDLE_PATH_KEY + - " to value " + relativeBundlePath + " in update package.", e); + throw new CodePushUnknownException("Unable to set key " + + RELATIVE_BUNDLE_PATH_KEY + " to value " + relativeBundlePath + + " in update package.", e); } updatePackage = CodePushUtils.convertJsonObjectToWritable(updatePackageJSON); @@ -331,6 +333,8 @@ public class CodePushPackage { while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) { bout.write(data, 0, numBytesRead); } + } catch (MalformedURLException e) { + throw new CodePushMalformedDataException(remoteBundleUrl, e); } finally { try { if (bout != null) bout.close(); @@ -338,7 +342,7 @@ public class CodePushPackage { if (bin != null) bin.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { - CodePushUtils.logException("Error closing IO resources.", e); + throw new CodePushUnknownException("Error closing IO resources.", e); } } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java new file mode 100644 index 0000000..cb99f6c --- /dev/null +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUnknownException.java @@ -0,0 +1,12 @@ +package com.microsoft.codepush.react; + +class CodePushUnknownException extends RuntimeException { + + public CodePushUnknownException(String message, Throwable cause) { + super(message, cause); + } + + public CodePushUnknownException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java index 4e9c84c..ee6d78d 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java @@ -36,7 +36,7 @@ public class CodePushUpdateUtils { manifest.add(relativePath + ":" + computeHash(new FileInputStream(file))); } catch (FileNotFoundException e) { // Should not happen. - CodePushUtils.logException("Unable to compute hash of update contents.", e); + throw new CodePushUnknownException("Unable to compute hash of update contents.", e); } } } @@ -52,7 +52,7 @@ public class CodePushUpdateUtils { while (digestInputStream.read(byteBuffer) != -1); } catch (NoSuchAlgorithmException | IOException e) { // Should not happen. - CodePushUtils.logException("Unable to compute hash of update contents.", e); + throw new CodePushUnknownException("Unable to compute hash of update contents.", e); } finally { try { if (digestInputStream != null) digestInputStream.close(); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java index 780f1db..55752a6 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java @@ -38,7 +38,7 @@ public class CodePushUtils { obj = jsonArr.get(i); } catch (JSONException jsonException) { // Should not happen. - CodePushUtils.logException(i + " should be within bounds of array " + jsonArr.toString(), jsonException); + throw new CodePushUnknownException(i + " should be within bounds of array " + jsonArr.toString(), jsonException); } if (obj instanceof JSONObject) @@ -56,7 +56,7 @@ public class CodePushUtils { else if (obj == null) arr.pushNull(); else - CodePushUtils.log("Unrecognized object: " + obj); + throw new CodePushUnknownException("Unrecognized object: " + obj); } return arr; @@ -72,7 +72,7 @@ public class CodePushUtils { obj = jsonObj.get(key); } catch (JSONException jsonException) { // Should not happen. - CodePushUtils.logException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException); + throw new CodePushUnknownException("Key " + key + " should exist in " + jsonObj.toString() + ".", jsonException); } if (obj instanceof JSONObject) @@ -90,7 +90,7 @@ public class CodePushUtils { else if (obj == null) map.putNull(key); else - CodePushUtils.log("Unrecognized object: " + obj); + throw new CodePushUnknownException("Unrecognized object: " + obj); } return map; @@ -124,7 +124,7 @@ public class CodePushUtils { try { jsonArr.put(number.doubleValue()); } catch (JSONException jsonException) { - CodePushUtils.log("Unable to put value " + arr.getDouble(i) + " in JSONArray"); + throw new CodePushUnknownException("Unable to put value " + arr.getDouble(i) + " in JSONArray"); } } break; @@ -167,10 +167,10 @@ public class CodePushUtils { jsonObj.put(key, null); break; default: - CodePushUtils.log("Unrecognized type: " + type + " of key: " + key); + throw new CodePushUnknownException("Unrecognized type: " + type + " of key: " + key); } } catch (JSONException jsonException) { - CodePushUtils.logException("Error setting key: " + key + " in JSONObject", jsonException); + throw new CodePushUnknownException("Error setting key: " + key + " in JSONObject", jsonException); } } @@ -203,8 +203,7 @@ public class CodePushUtils { return convertJsonObjectToWritable(json); } catch (JSONException jsonException) { // Should not happen - CodePushUtils.logException(filePath, jsonException); - return null; + throw new CodePushMalformedDataException(filePath, jsonException); } } @@ -216,11 +215,6 @@ public class CodePushUtils { log("Loading JS bundle from \"" + path + "\""); } - public static void logException(String message, Exception e) { - log(message); - e.printStackTrace(); - } - public static String tryGetString(ReadableMap map, String key) { try { return map.getString(key); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java index fe9767b..fcc8cf5 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java @@ -47,7 +47,7 @@ public class FileUtils { if (fromBufferedStream != null) fromBufferedStream.close(); if (destStream != null) destStream.close(); } catch (IOException e) { - CodePushUtils.logException("Error closing IO resources.", e); + throw new CodePushUnknownException("Error closing IO resources.", e); } } } @@ -110,8 +110,8 @@ public class FileUtils { File newFilePath = new File(newFolderPath, newFileName); if (!fileToMove.renameTo(newFilePath)) { - CodePushUtils.log("Unable to move file from " + fileToMove.getAbsolutePath() + - " to " + newFilePath.getAbsolutePath() + "."); + throw new CodePushUnknownException("Unable to move file from " + + fileToMove.getAbsolutePath() + " to " + newFilePath.getAbsolutePath() + "."); } } @@ -185,7 +185,7 @@ public class FileUtils { if (bufferedStream != null) bufferedStream.close(); if (fileStream != null) fileStream.close(); } catch (IOException e) { - CodePushUtils.logException("Error closing IO resources.", e); + throw new CodePushUnknownException("Error closing IO resources.", e); } } } From ac42b44ba9ae9ee7987bc93dbfdbe345b1b97f8d Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Tue, 26 Apr 2016 11:31:25 -0700 Subject: [PATCH 37/53] Fixing isFirstRun on Android --- .../main/java/com/microsoft/codepush/react/CodePush.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 c071928..e91f7bd 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 @@ -233,9 +233,12 @@ public class CodePush implements ReactPackage { } private void initializeUpdateAfterRestart() { + // Re-set the state that indicates that + // the app was just updated. + didUpdate = false; + JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { - didUpdate = true; try { boolean updateIsLoading = pendingUpdate.getBoolean(PENDING_UPDATE_IS_LOADING_KEY); if (updateIsLoading) { @@ -245,6 +248,10 @@ public class CodePush implements ReactPackage { needToReportRollback = true; rollbackPackage(); } else { + // There is in fact a new update running for the first + // time, so update the local state to ensure isFirstRun works + didUpdate = true; + // Mark that we tried to initialize the new update, so that if it crashes, // we will know that we need to rollback when the app next starts. savePendingUpdate(pendingUpdate.getString(PENDING_UPDATE_HASH_KEY), From c5a84d1691bcf622b1c4acf3ddfa71ba8e21e645 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Tue, 26 Apr 2016 11:36:09 -0700 Subject: [PATCH 38/53] Tweak comments --- .../main/java/com/microsoft/codepush/react/CodePush.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e91f7bd..8e4e555 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 @@ -233,8 +233,8 @@ public class CodePush implements ReactPackage { } private void initializeUpdateAfterRestart() { - // Re-set the state that indicates that - // the app was just updated. + // Reset the state which indicates that + // the app was just freshly updated. didUpdate = false; JSONObject pendingUpdate = getPendingUpdate(); @@ -249,7 +249,7 @@ public class CodePush implements ReactPackage { rollbackPackage(); } else { // There is in fact a new update running for the first - // time, so update the local state to ensure isFirstRun works + // time, so update the local state to ensure the client knows. didUpdate = true; // Mark that we tried to initialize the new update, so that if it crashes, From 33a7096c10400554765914499cf30d9baf325e67 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Tue, 26 Apr 2016 11:57:12 -0700 Subject: [PATCH 39/53] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 22ee925..7afef8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-code-push", - "version": "1.10.4-beta", + "version": "1.10.5-beta", "description": "React Native plugin for the CodePush service", "main": "CodePush.js", "homepage": "https://microsoft.github.io/code-push", From ca7e0f5b0cbc7501644b71555915388d99050014 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Wed, 27 Apr 2016 08:49:51 -0700 Subject: [PATCH 40/53] Adding platform support detail --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 612cc94..217d260 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ In order to ensure that your end users always have a functioning version of your ## Supported React Native platforms -- iOS -- Android +- iOS (7+) +- Android (4.1+) We try our best to maintain backwards compatability of our plugin with previous versions of React Native, but due to the nature of the platform, and the existence of breaking changes between releases, it is possible that you need to use a specific version of the CodePush plugin in order to support the exact version of React Native you are using. The following table outlines which CodePush plugin versions officially support the respective React Native versions: From 4508f630480ff871af8b20a1100398210bdf77cd Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 14:29:38 -0700 Subject: [PATCH 41/53] improve download progress perf --- CodePush.js | 92 +++++++++---------- .../microsoft/codepush/react/CodePush.java | 34 ++++++- ios/CodePush/CodePush.h | 3 + ios/CodePush/CodePush.m | 49 +++++++--- ios/CodePush/CodePushDownloadHandler.m | 8 +- ios/CodePush/CodePushPackage.m | 2 + package-mixins.js | 12 +-- 7 files changed, 126 insertions(+), 74 deletions(-) diff --git a/CodePush.js b/CodePush.js index e31558c..eee79d8 100644 --- a/CodePush.js +++ b/CodePush.js @@ -9,7 +9,7 @@ const PackageMixins = require("./package-mixins")(NativeCodePush); async function checkForUpdate(deploymentKey = null) { /* * Before we ask the server if an update exists, we - * need to retrieve three pieces of information from the + * need to retrieve three pieces of information from the * native side: deployment key, app version (e.g. 1.0.1) * and the hash of the currently running update (if there is one). * This allows the client to only receive updates which are targetted @@ -17,7 +17,7 @@ async function checkForUpdate(deploymentKey = null) { * different from the CodePush update they have already installed. */ const nativeConfig = await getConfiguration(); - + /* * If a deployment key was explicitly provided, * then let's override the one we retrieved @@ -30,7 +30,7 @@ async function checkForUpdate(deploymentKey = null) { // Use dynamically overridden getCurrentPackage() during tests. const localPackage = await module.exports.getCurrentPackage(); - + /* * If the app has a previously installed update, and that update * was targetted at the same app version that is currently running, @@ -48,9 +48,9 @@ async function checkForUpdate(deploymentKey = null) { queryPackage.packageHash = config.packageHash; } } - + const update = await sdk.queryUpdateWithCurrentPackage(queryPackage); - + /* * There are four cases where checkForUpdate will resolve to null: * ---------------------------------------------------------------- @@ -69,13 +69,13 @@ async function checkForUpdate(deploymentKey = null) { * because we want to avoid having to install diff updates against the binary's * version, which we can't do yet on Android. */ - if (!update || update.updateAppVersion || - localPackage && (update.packageHash === localPackage.packageHash) || + if (!update || update.updateAppVersion || + localPackage && (update.packageHash === localPackage.packageHash) || (!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) { if (update && update.updateAppVersion) { log("An update is available but it is targeting a newer binary version than you are currently running."); } - + return null; } else { const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) }; @@ -93,7 +93,7 @@ const getConfiguration = (() => { } else if (testConfig) { return testConfig; } else { - config = await NativeCodePush.getConfiguration(); + config = await NativeCodePush.getConfiguration(); return config; } } @@ -123,7 +123,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(update); } - }); + }); }); }; @@ -135,7 +135,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(); } - }); + }); }); }; @@ -147,7 +147,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(); } - }); + }); }); }; @@ -159,7 +159,7 @@ function log(message) { console.log(`[CodePush] ${message}`) } -// This ensures that notifyApplicationReadyInternal is only called once +// This ensures that notifyApplicationReadyInternal is only called once // in the lifetime of this module instance. const notifyApplicationReady = (() => { let notifyApplicationReadyPromise; @@ -167,13 +167,13 @@ const notifyApplicationReady = (() => { if (!notifyApplicationReadyPromise) { notifyApplicationReadyPromise = notifyApplicationReadyInternal(); } - + return notifyApplicationReadyPromise; }; })(); async function notifyApplicationReadyInternal() { - await NativeCodePush.notifyApplicationReady(); + await NativeCodePush.notifyApplicationReady(); const statusReport = await NativeCodePush.getNewStatusReport(); if (statusReport) { const config = await getConfiguration(); @@ -208,15 +208,15 @@ function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) { const sync = (() => { let syncInProgress = false; const setSyncCompleted = () => { syncInProgress = false; }; - + return (options = {}, syncStatusChangeCallback, downloadProgressCallback) => { if (syncInProgress) { typeof syncStatusChangeCallback === "function" ? syncStatusChangeCallback(CodePush.SyncStatus.SYNC_IN_PROGRESS) : log("Sync already in progress."); return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS); - } - + } + syncInProgress = true; const syncPromise = syncInternal(options, syncStatusChangeCallback, downloadProgressCallback); syncPromise @@ -230,7 +230,7 @@ const sync = (() => { /* * The syncInternal method provides a simple, one-line experience for * incorporating the check, download and installation of an update. - * + * * It simply composes the existing API methods together and adds additional * support for respecting mandatory updates, ignoring previously failed * releases, and displaying a standard confirmation UI to the end-user @@ -245,9 +245,9 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE, minimumBackgroundDuration: 0, updateDialog: null, - ...options + ...options }; - + syncStatusChangeCallback = typeof syncStatusChangeCallback === "function" ? syncStatusChangeCallback : (syncStatus) => { @@ -271,7 +271,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg log("User cancelled the update."); break; case CodePush.SyncStatus.UPDATE_INSTALLED: - /* + /* * If the install mode is IMMEDIATE, this will not get returned as the * app will be restarted to a new Javascript context. */ @@ -290,40 +290,34 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg break; } }; - - downloadProgressCallback = typeof downloadProgressCallback === "function" - ? downloadProgressCallback - : (downloadProgress) => { - log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`); - }; - + try { await CodePush.notifyApplicationReady(); - + syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE); const remotePackage = await checkForUpdate(syncOptions.deploymentKey); - + const doDownloadAndInstall = async () => { syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE); const localPackage = await remotePackage.download(downloadProgressCallback); - + // Determine the correct install mode based on whether the update is mandatory or not. resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode; - + syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE); await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => { syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED); }); - + return CodePush.SyncStatus.UPDATE_INSTALLED; }; - + const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates); if (!remotePackage || updateShouldBeIgnored) { if (updateShouldBeIgnored) { log("An update is available, but it is being ignored due to having been previously rolled back."); } - + syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE); return CodePush.SyncStatus.UP_TO_DATE; } else if (syncOptions.updateDialog) { @@ -334,24 +328,24 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } else { syncOptions.updateDialog = { ...CodePush.DEFAULT_UPDATE_DIALOG, ...syncOptions.updateDialog }; } - - return await new Promise((resolve, reject) => { + + return await new Promise((resolve, reject) => { let message = null; const dialogButtons = [{ text: null, - onPress: async () => { + onPress: async () => { resolve(await doDownloadAndInstall()); } }]; - + if (remotePackage.isMandatory) { message = syncOptions.updateDialog.mandatoryUpdateMessage; dialogButtons[0].text = syncOptions.updateDialog.mandatoryContinueButtonLabel; } else { message = syncOptions.updateDialog.optionalUpdateMessage; - dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel; + dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel; // Since this is an optional update, add another button - // to allow the end-user to ignore it + // to allow the end-user to ignore it dialogButtons.push({ text: syncOptions.updateDialog.optionalIgnoreButtonLabel, onPress: () => { @@ -360,13 +354,13 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } }); } - + // If the update has a description, and the developer // explicitly chose to display it, then set that as the message if (syncOptions.updateDialog.appendReleaseDescription && remotePackage.description) { - message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`; + message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`; } - + syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION); Alert.alert(syncOptions.updateDialog.title, message, dialogButtons); }); @@ -375,16 +369,16 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } } catch (error) { syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR); - log(error.message); + log(error.message); throw error; - } + } }; let CodePush; -// If the "NativeCodePush" variable isn't defined, then +// If the "NativeCodePush" variable isn't defined, then // the app didn't properly install the native module, -// and therefore, it doesn't make sense initializing +// and therefore, it doesn't make sense initializing // the JS interface when it wouldn't work anyways. if (NativeCodePush) { CodePush = { 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 8e4e555..24d9a38 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 @@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.uimanager.ReactChoreographer; import com.facebook.react.uimanager.ViewManager; import com.facebook.soloader.SoLoader; @@ -25,6 +26,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.provider.Settings; +import android.view.Choreographer; import org.json.JSONArray; import org.json.JSONException; @@ -439,7 +441,7 @@ public class CodePush implements ReactPackage { } @ReactMethod - public void downloadUpdate(final ReadableMap updatePackage, final Promise promise) { + public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -447,11 +449,35 @@ public class CodePush implements ReactPackage { WritableMap mutableUpdatePackage = CodePushUtils.convertReadableMapToWritableMap(updatePackage); mutableUpdatePackage.putString(BINARY_MODIFIED_TIME_KEY, "" + getBinaryResourcesModifiedTime()); codePushPackage.downloadPackage(mutableUpdatePackage, CodePush.this.assetsBundleFileName, new DownloadProgressCallback() { + private boolean nextFrameBusy = false; + private DownloadProgress latestDownloadProgress = null; + @Override public void call(DownloadProgress downloadProgress) { - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap()); + if (!notifyProgress) { + return; + } + + this.latestDownloadProgress = downloadProgress; + if (nextFrameBusy) { + return; + } + + nextFrameBusy = true; + mainActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + nextFrameBusy = false; + } + }); + } + }); } }); diff --git a/ios/CodePush/CodePush.h b/ios/CodePush/CodePush.h index b56561e..f94d684 100644 --- a/ios/CodePush/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -54,11 +54,13 @@ @property (strong) NSOutputStream *outputFileStream; @property long long expectedContentLength; @property long long receivedContentLength; +@property dispatch_queue_t usingQueue; @property (copy) void (^progressCallback)(long long, long long); @property (copy) void (^doneCallback)(BOOL); @property (copy) void (^failCallback)(NSError *err); - (id)init:(NSString *)downloadFilePath +usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback; @@ -78,6 +80,7 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName + usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback; diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 689f943..a91120d 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -7,7 +7,7 @@ #import "CodePush.h" -@interface CodePush () +@interface CodePush () @end @implementation CodePush { @@ -171,6 +171,8 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; +@synthesize pauseCallback = _pauseCallback; +@synthesize paused = _paused; /* * This method is used to clear updates that are installed @@ -273,7 +275,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - + _paused = YES; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -479,6 +481,7 @@ static NSString *bundleResourceName = @"main"; * This is native-side of the RemotePackage.download method */ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage + notifyProgress:(BOOL)notifyProgress resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { @@ -488,21 +491,18 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [mutableUpdatePackage setValue:[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL] forKey:BinaryBundleDateKey]; } - + [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] + usingQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - dispatch_async(_methodQueue, ^{ - // Notify the script-side about the progress - [self.bridge.eventDispatcher - sendDeviceEventWithName:@"CodePushDownloadProgress" - body:@{ - @"totalBytes":[NSNumber numberWithLongLong:expectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength] - }]; - }); + // Notify the script-side about the progress + if (notifyProgress) { + [self sendDownloadProgressDuringNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; + } } // The download completed doneCallback:^{ @@ -760,4 +760,29 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve resolve(nil); } +#pragma mark - Methods for handling dispatching of download progress events to JS (Private) + +long long latestExpectedContentLength = -1; +long long latestReceivedConentLength = -1; + +- (void)didUpdateFrame:(RCTFrameUpdate *)update +{ + // Notify the script-side about the progress + [self.bridge.eventDispatcher + sendDeviceEventWithName:@"CodePushDownloadProgress" + body:@{ + @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + }]; + _paused = YES; +} + +- (void)sendDownloadProgressDuringNextFrame:(long long)expectedContentLength + receivedContentLength:(long long)receivedContentLength +{ + latestExpectedContentLength = expectedContentLength; + latestReceivedConentLength = receivedContentLength; + _paused = NO; +} + @end \ No newline at end of file diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 62fd6f4..69c245e 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -6,12 +6,14 @@ } - (id)init:(NSString *)downloadFilePath +usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback { self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath append:NO]; self.receivedContentLength = 0; + self.usingQueue = usingQueue; self.progressCallback = progressCallback; self.doneCallback = doneCallback; self.failCallback = failCallback; @@ -22,12 +24,12 @@ failCallback:(void (^)(NSError *err))failCallback { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; - + NSOperationQueue *delegateQueue = [NSOperationQueue new]; + delegateQueue.underlyingQueue = self.usingQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; - [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] - forMode:NSDefaultRunLoopMode]; + [connection setDelegateQueue:delegateQueue]; [connection start]; } diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index d72f30b..63f5250 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -41,6 +41,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName + usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback @@ -71,6 +72,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc] init:downloadFilePath + usingQueue:usingQueue progressCallback:progressCallback doneCallback:^(BOOL isZip) { NSError *error = nil; diff --git a/package-mixins.js b/package-mixins.js index 4c68404..0357c41 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -14,24 +14,24 @@ module.exports = (NativeCodePush) => { let downloadProgressSubscription; if (downloadProgressCallback) { - // Use event subscription to obtain download progress. + // 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); + try { + const downloadedPackage = await NativeCodePush.downloadUpdate(this, !!downloadProgressCallback); reportStatusDownload && reportStatusDownload(this); return { ...downloadedPackage, ...local }; } finally { downloadProgressSubscription && downloadProgressSubscription.remove(); } }, - + isPending: false // A remote package could never be in a pending state }; }; @@ -47,7 +47,7 @@ module.exports = (NativeCodePush) => { localPackage.isPending = true; // Mark the package as pending since it hasn't been applied yet } }, - + isPending: false // A local package wouldn't be pending until it was installed }; From 58d5d89c78c73b7d57912c896e766f3fd70291e7 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 15:20:42 -0700 Subject: [PATCH 42/53] Move delegateQueue creation --- ios/CodePush/CodePushDownloadHandler.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 69c245e..2268020 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -24,11 +24,11 @@ failCallback:(void (^)(NSError *err))failCallback { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; - NSOperationQueue *delegateQueue = [NSOperationQueue new]; - delegateQueue.underlyingQueue = self.usingQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + NSOperationQueue *delegateQueue = [NSOperationQueue new]; + delegateQueue.underlyingQueue = self.usingQueue; [connection setDelegateQueue:delegateQueue]; [connection start]; } From 99da3bf1b8fda9ef15be7af17e888d81dbf06452 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 16:00:15 -0700 Subject: [PATCH 43/53] download-progress-perf --- ios/CodePush/CodePush.h | 6 ++--- ios/CodePush/CodePush.m | 34 ++++++++++++-------------- ios/CodePush/CodePushDownloadHandler.m | 6 ++--- ios/CodePush/CodePushPackage.m | 4 +-- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/ios/CodePush/CodePush.h b/ios/CodePush/CodePush.h index f94d684..06ac51b 100644 --- a/ios/CodePush/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -54,13 +54,13 @@ @property (strong) NSOutputStream *outputFileStream; @property long long expectedContentLength; @property long long receivedContentLength; -@property dispatch_queue_t usingQueue; +@property dispatch_queue_t operationQueue; @property (copy) void (^progressCallback)(long long, long long); @property (copy) void (^doneCallback)(BOOL); @property (copy) void (^failCallback)(NSError *err); - (id)init:(NSString *)downloadFilePath -usingQueue:(dispatch_queue_t)usingQueue +operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback; @@ -80,7 +80,7 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName - usingQueue:(dispatch_queue_t)usingQueue + operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback; diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a91120d..c0cce17 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -495,37 +495,33 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] - usingQueue:_methodQueue + operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Notify the script-side about the progress if (notifyProgress) { - [self sendDownloadProgressDuringNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; + [self updateDownloadProgressForNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; } } // The download completed doneCallback:^{ - dispatch_async(_methodQueue, ^{ - NSError *err; - NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err]; + NSError *err; + NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err]; - if (err) { - return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); - } + if (err) { + return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); + } - resolve(newPackage); - }); + resolve(newPackage); } // The download failed failCallback:^(NSError *err) { - dispatch_async(_methodQueue, ^{ - if ([CodePushErrorUtils isCodePushError:err]) { - [self saveFailedUpdate:mutableUpdatePackage]; - } + if ([CodePushErrorUtils isCodePushError:err]) { + [self saveFailedUpdate:mutableUpdatePackage]; + } - reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); - }); + reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -777,8 +773,8 @@ long long latestReceivedConentLength = -1; _paused = YES; } -- (void)sendDownloadProgressDuringNextFrame:(long long)expectedContentLength - receivedContentLength:(long long)receivedContentLength +- (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength + receivedContentLength:(long long)receivedContentLength { latestExpectedContentLength = expectedContentLength; latestReceivedConentLength = receivedContentLength; diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 69c245e..3e69381 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -6,14 +6,14 @@ } - (id)init:(NSString *)downloadFilePath -usingQueue:(dispatch_queue_t)usingQueue +operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback { self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath append:NO]; self.receivedContentLength = 0; - self.usingQueue = usingQueue; + self.operationQueue = operationQueue; self.progressCallback = progressCallback; self.doneCallback = doneCallback; self.failCallback = failCallback; @@ -25,7 +25,7 @@ failCallback:(void (^)(NSError *err))failCallback { cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; NSOperationQueue *delegateQueue = [NSOperationQueue new]; - delegateQueue.underlyingQueue = self.usingQueue; + delegateQueue.underlyingQueue = self.operationQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index 63f5250..2d15a28 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -41,7 +41,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName - usingQueue:(dispatch_queue_t)usingQueue + operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback @@ -72,7 +72,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc] init:downloadFilePath - usingQueue:usingQueue + operationQueue:operationQueue progressCallback:progressCallback doneCallback:^(BOOL isZip) { NSError *error = nil; From 6a8ce4d041a96a14fa61e4985ecdbf42df11a146 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 16:04:07 -0700 Subject: [PATCH 44/53] rename pragma mark --- ios/CodePush/CodePush.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index c0cce17..a4c87bd 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -756,7 +756,7 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve resolve(nil); } -#pragma mark - Methods for handling dispatching of download progress events to JS (Private) +#pragma mark - RCTFrameUpdateObserver Methods long long latestExpectedContentLength = -1; long long latestReceivedConentLength = -1; From 951d6ca564e4905656f1baae9255f120491f740a Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 17:41:15 -0700 Subject: [PATCH 45/53] nextFrameBusy -> hasScheduledNextFrame --- .../java/com/microsoft/codepush/react/CodePush.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 24d9a38..486b1ed 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 @@ -238,7 +238,7 @@ public class CodePush implements ReactPackage { // Reset the state which indicates that // the app was just freshly updated. didUpdate = false; - + JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { try { @@ -253,7 +253,7 @@ public class CodePush implements ReactPackage { // There is in fact a new update running for the first // time, so update the local state to ensure the client knows. didUpdate = true; - + // Mark that we tried to initialize the new update, so that if it crashes, // we will know that we need to rollback when the app next starts. savePendingUpdate(pendingUpdate.getString(PENDING_UPDATE_HASH_KEY), @@ -449,7 +449,7 @@ public class CodePush implements ReactPackage { WritableMap mutableUpdatePackage = CodePushUtils.convertReadableMapToWritableMap(updatePackage); mutableUpdatePackage.putString(BINARY_MODIFIED_TIME_KEY, "" + getBinaryResourcesModifiedTime()); codePushPackage.downloadPackage(mutableUpdatePackage, CodePush.this.assetsBundleFileName, new DownloadProgressCallback() { - private boolean nextFrameBusy = false; + private boolean hasScheduledNextFrame = false; private DownloadProgress latestDownloadProgress = null; @Override @@ -459,11 +459,11 @@ public class CodePush implements ReactPackage { } this.latestDownloadProgress = downloadProgress; - if (nextFrameBusy) { + if (hasScheduledNextFrame) { return; } - nextFrameBusy = true; + hasScheduledNextFrame = true; mainActivity.runOnUiThread(new Runnable() { @Override public void run() { @@ -473,7 +473,7 @@ public class CodePush implements ReactPackage { getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); - nextFrameBusy = false; + hasScheduledNextFrame = false; } }); } From b1a176ace51a27982ae91dcba4ea3c9c14ed1dde Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 11:29:38 -0700 Subject: [PATCH 46/53] CR feedback --- ios/CodePush/CodePush.m | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a4c87bd..a3b2cd5 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -492,6 +492,10 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage forKey:BinaryBundleDateKey]; } + if (notifyProgress) { + [self setupFrameObserverForDownloadProgress]; + } + [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] @@ -499,10 +503,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Notify the script-side about the progress - if (notifyProgress) { - [self updateDownloadProgressForNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; - } + [self updateDownloadProgressForNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; } // The download completed doneCallback:^{ @@ -512,7 +514,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (err) { return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } - + + [self pauseFrameObserver]; resolve(newPackage); } // The download failed @@ -520,7 +523,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if ([CodePushErrorUtils isCodePushError:err]) { [self saveFailedUpdate:mutableUpdatePackage]; } - + + [self pauseFrameObserver]; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -760,9 +764,14 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve long long latestExpectedContentLength = -1; long long latestReceivedConentLength = -1; +BOOL didUpdateProgress = NO; - (void)didUpdateFrame:(RCTFrameUpdate *)update { + if (!didUpdateProgress) { + return; + } + // Notify the script-side about the progress [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" @@ -770,7 +779,8 @@ long long latestReceivedConentLength = -1; @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] }]; - _paused = YES; + didUpdateProgress = NO; + } - (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength @@ -778,7 +788,19 @@ long long latestReceivedConentLength = -1; { latestExpectedContentLength = expectedContentLength; latestReceivedConentLength = receivedContentLength; + didUpdateProgress = YES; +} + + +- (void)setupFrameObserverForDownloadProgress +{ + didUpdateProgress = NO; _paused = NO; } +- (void)pauseFrameObserver +{ + _paused = YES; +} + @end \ No newline at end of file From a995a3c64d48911638a0e17826e94ecad1b8e5b8 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 14:26:09 -0700 Subject: [PATCH 47/53] CR feedback --- .../microsoft/codepush/react/CodePush.java | 23 +++++++++++++++---- .../codepush/react/DownloadProgress.java | 4 ++++ ios/CodePush/CodePush.m | 21 +++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) 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 486b1ed..49f0c12 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 @@ -458,27 +458,40 @@ public class CodePush implements ReactPackage { return; } - this.latestDownloadProgress = downloadProgress; + latestDownloadProgress = downloadProgress; + // If the download is completed, synchronously send the last event. + if (latestDownloadProgress.isCompleted()) { + dispatchDownloadProgressEvent(); + return; + } + if (hasScheduledNextFrame) { return; } hasScheduledNextFrame = true; - mainActivity.runOnUiThread(new Runnable() { + getReactApplicationContext().runOnUiQueueThread(new Runnable() { @Override public void run() { ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + if (!latestDownloadProgress.isCompleted()) { + dispatchDownloadProgressEvent(); + } + hasScheduledNextFrame = false; } }); } }); } + + public void dispatchDownloadProgressEvent() { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + } }); WritableMap newPackage = codePushPackage.getPackage(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY)); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java b/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java index ff4add9..45b5026 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java @@ -23,4 +23,8 @@ class DownloadProgress { } return map; } + + public boolean isCompleted() { + return this.totalBytes == this.receivedBytes; + } } diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a3b2cd5..0c27c75 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -275,7 +275,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - _paused = YES; + [self pauseFrameObserver]; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -502,9 +502,13 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - // Notify the script-side about the progress [self updateDownloadProgressForNextFrame:expectedContentLength receivedContentLength:receivedContentLength]; + // If the download is completed, stop observing frame updates and synchronously send the last event. + if (expectedContentLength == receivedContentLength) { + [self pauseFrameObserver]; + [self dispatchDownloadProgressEvent]; + } } // The download completed doneCallback:^{ @@ -514,8 +518,6 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (err) { return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } - - [self pauseFrameObserver]; resolve(newPackage); } // The download failed @@ -772,15 +774,19 @@ BOOL didUpdateProgress = NO; return; } + [self dispatchDownloadProgressEvent]; + didUpdateProgress = NO; +} + +- (void)dispatchDownloadProgressEvent +{ // Notify the script-side about the progress [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" body:@{ @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] - }]; - didUpdateProgress = NO; - + }]; } - (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength @@ -800,6 +806,7 @@ BOOL didUpdateProgress = NO; - (void)pauseFrameObserver { + didUpdateProgress = NO; _paused = YES; } From ca54397aac7d10d7ab8a7524806385f63434ab85 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Thu, 28 Apr 2016 15:01:17 -0700 Subject: [PATCH 48/53] Exclude CodePush assets from backups --- ios/CodePush/CodePushPackage.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index d72f30b..80a481c 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -60,6 +60,11 @@ static NSString *const UnzippedFolderName = @"unzipped"; withIntermediateDirectories:YES attributes:nil error:&error]; + + // Ensure that none of the CodePush updates we store on disk are + // ever included in the end users iTunes and/or iCloud backups + NSURL *codePushURL = [NSURL fileURLWithPath:[self getCodePushPath]]; + [codePushURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; } if (error) { From 3b050738fc69a7868adaddfaa137e2fa8f88e423 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 16:57:06 -0700 Subject: [PATCH 49/53] inline progress update methods --- ios/CodePush/CodePush.m | 74 +++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 0c27c75..2666edc 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,6 +15,9 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; + long long latestExpectedContentLength; + long long latestReceivedConentLength; + BOOL didUpdateProgress; } RCT_EXPORT_MODULE() @@ -225,6 +228,17 @@ static NSString *bundleResourceName = @"main"; [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (void)dispatchDownloadProgressEvent +{ + // Notify the script-side about the progress + [self.bridge.eventDispatcher + sendDeviceEventWithName:@"CodePushDownloadProgress" + body:@{ + @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + }]; +} + /* * This method ensures that the app was packaged with a JS bundle * file, and if not, it throws the appropriate exception. @@ -275,7 +289,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - [self pauseFrameObserver]; + _paused = YES; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -493,7 +507,10 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage } if (notifyProgress) { - [self setupFrameObserverForDownloadProgress]; + // Set up and unpause the frame observer so that it can emit + // progress events every frame if the progress is updated. + didUpdateProgress = NO; + _paused = NO; } [CodePushPackage @@ -502,11 +519,16 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - [self updateDownloadProgressForNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; - // If the download is completed, stop observing frame updates and synchronously send the last event. + // Update the download progress so that the frame observer can notify the JS side + latestExpectedContentLength = expectedContentLength; + latestReceivedConentLength = receivedContentLength; + didUpdateProgress = YES; + + // If the download is completed, stop observing frame + // updates and synchronously send the last event. if (expectedContentLength == receivedContentLength) { - [self pauseFrameObserver]; + didUpdateProgress = NO; + _paused = YES; [self dispatchDownloadProgressEvent]; } } @@ -526,7 +548,9 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [self saveFailedUpdate:mutableUpdatePackage]; } - [self pauseFrameObserver]; + // Stop observing frame updates if the download fails. + didUpdateProgress = NO; + _paused = YES; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -764,10 +788,6 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve #pragma mark - RCTFrameUpdateObserver Methods -long long latestExpectedContentLength = -1; -long long latestReceivedConentLength = -1; -BOOL didUpdateProgress = NO; - - (void)didUpdateFrame:(RCTFrameUpdate *)update { if (!didUpdateProgress) { @@ -778,36 +798,4 @@ BOOL didUpdateProgress = NO; didUpdateProgress = NO; } -- (void)dispatchDownloadProgressEvent -{ - // Notify the script-side about the progress - [self.bridge.eventDispatcher - sendDeviceEventWithName:@"CodePushDownloadProgress" - body:@{ - @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] - }]; -} - -- (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength - receivedContentLength:(long long)receivedContentLength -{ - latestExpectedContentLength = expectedContentLength; - latestReceivedConentLength = receivedContentLength; - didUpdateProgress = YES; -} - - -- (void)setupFrameObserverForDownloadProgress -{ - didUpdateProgress = NO; - _paused = NO; -} - -- (void)pauseFrameObserver -{ - didUpdateProgress = NO; - _paused = YES; -} - @end \ No newline at end of file From 5cc0556d1a66ed2b97c4f26b13ced745b1106751 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 17:00:27 -0700 Subject: [PATCH 50/53] add underscore prefix --- ios/CodePush/CodePush.m | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 2666edc..d696de8 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,9 +15,9 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; - long long latestExpectedContentLength; - long long latestReceivedConentLength; - BOOL didUpdateProgress; + long long _latestExpectedContentLength; + long long _latestReceivedConentLength; + BOOL _didUpdateProgress; } RCT_EXPORT_MODULE() @@ -174,7 +174,6 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; -@synthesize pauseCallback = _pauseCallback; @synthesize paused = _paused; /* @@ -234,8 +233,8 @@ static NSString *bundleResourceName = @"main"; [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" body:@{ - @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + @"totalBytes":[NSNumber numberWithLongLong:_latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:_latestReceivedConentLength] }]; } @@ -509,7 +508,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (notifyProgress) { // Set up and unpause the frame observer so that it can emit // progress events every frame if the progress is updated. - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = NO; } @@ -520,14 +519,14 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Update the download progress so that the frame observer can notify the JS side - latestExpectedContentLength = expectedContentLength; - latestReceivedConentLength = receivedContentLength; - didUpdateProgress = YES; + _latestExpectedContentLength = expectedContentLength; + _latestReceivedConentLength = receivedContentLength; + _didUpdateProgress = YES; // If the download is completed, stop observing frame // updates and synchronously send the last event. if (expectedContentLength == receivedContentLength) { - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = YES; [self dispatchDownloadProgressEvent]; } @@ -549,7 +548,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage } // Stop observing frame updates if the download fails. - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = YES; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; @@ -790,12 +789,12 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve - (void)didUpdateFrame:(RCTFrameUpdate *)update { - if (!didUpdateProgress) { + if (!_didUpdateProgress) { return; } [self dispatchDownloadProgressEvent]; - didUpdateProgress = NO; + _didUpdateProgress = NO; } @end \ No newline at end of file From 27e9f349911f96c253dc478324ed448b89f4f91f Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 17:10:23 -0700 Subject: [PATCH 51/53] add comment and resynthesize pauseCallback --- ios/CodePush/CodePush.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index d696de8..10ece53 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,6 +15,8 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; + + // Used to coordinate the dispatching of download progress events to JS. long long _latestExpectedContentLength; long long _latestReceivedConentLength; BOOL _didUpdateProgress; @@ -174,6 +176,7 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; +@synthesize pauseCallback = _pauseCallback; @synthesize paused = _paused; /* From bd5ba5fb781436b2e925713e1c8affabbbdebb81 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Thu, 28 Apr 2016 18:03:12 -0700 Subject: [PATCH 52/53] Bumping the version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7afef8d..0b8681e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-code-push", - "version": "1.10.5-beta", + "version": "1.10.6-beta", "description": "React Native plugin for the CodePush service", "main": "CodePush.js", "homepage": "https://microsoft.github.io/code-push", From 5ca61cd116d71ca757764355c7adcc9567ab57ba Mon Sep 17 00:00:00 2001 From: Eternal_Jovi Date: Fri, 29 Apr 2016 14:48:00 +0800 Subject: [PATCH 53/53] fix progress object param --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 217d260..87df6e6 100644 --- a/README.md +++ b/README.md @@ -640,7 +640,7 @@ codePush.sync({ updateDialog: true }, break; } }, - (totalBytes, receivedBytes) => { + ({ receivedBytes, totalBytes, }) => { /* Update download modal progress */ } );