From 3086aeae08b3b9b2eb916d6678c8a6b8deab25ec Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Fri, 15 Apr 2016 15:24:22 -0700 Subject: [PATCH] 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