// Copyright © 2019 650 Industries. All rights reserved. #import #import NS_ASSUME_NONNULL_BEGIN NSString * const EXUpdatesEmbeddedManifestName = @"app"; NSString * const EXUpdatesEmbeddedManifestType = @"manifest"; NSString * const EXUpdatesEmbeddedBundleFilename = @"app"; NSString * const EXUpdatesEmbeddedBundleFileType = @"bundle"; NSString * const EXUpdatesBareEmbeddedBundleFilename = @"main"; NSString * const EXUpdatesBareEmbeddedBundleFileType = @"jsbundle"; static NSString * const EXUpdatesEmbeddedAppLoaderErrorDomain = @"EXUpdatesEmbeddedAppLoader"; @implementation EXUpdatesEmbeddedAppLoader + (nullable EXUpdatesUpdate *)embeddedManifestWithConfig:(EXUpdatesConfig *)config database:(nullable EXUpdatesDatabase *)database { static EXUpdatesUpdate *embeddedManifest; static dispatch_once_t once; dispatch_once(&once, ^{ if (!config.hasEmbeddedUpdate) { embeddedManifest = nil; } else if (!embeddedManifest) { NSString *path = [[NSBundle mainBundle] pathForResource:EXUpdatesEmbeddedManifestName ofType:EXUpdatesEmbeddedManifestType]; NSData *manifestData = [NSData dataWithContentsOfFile:path]; if (!manifestData) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"The embedded manifest is invalid or could not be read. Make sure you have configured expo-updates correctly in your Xcode Build Phases." userInfo:@{}]; } NSError *err; id manifest = [NSJSONSerialization JSONObjectWithData:manifestData options:kNilOptions error:&err]; if (!manifest) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"The embedded manifest is invalid or could not be read. Make sure you have configured expo-updates correctly in your Xcode Build Phases." userInfo:@{}]; } else { NSAssert([manifest isKindOfClass:[NSDictionary class]], @"embedded manifest should be a valid JSON file"); NSMutableDictionary *mutableManifest = [manifest mutableCopy]; // automatically verify embedded manifest since it was already codesigned mutableManifest[@"isVerified"] = @(YES); embeddedManifest = [EXUpdatesUpdate updateWithEmbeddedManifest:[mutableManifest copy] config:config database:database]; if (!embeddedManifest.updateId) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"The embedded manifest is invalid. Make sure you have configured expo-updates correctly in your Xcode Build Phases." userInfo:@{}]; } } } }); return embeddedManifest; } - (void)loadUpdateFromEmbeddedManifestWithCallback:(EXUpdatesAppLoaderManifestBlock)manifestBlock success:(EXUpdatesAppLoaderSuccessBlock)success error:(EXUpdatesAppLoaderErrorBlock)error { EXUpdatesUpdate *embeddedManifest = [[self class] embeddedManifestWithConfig:self.config database:self.database]; if (embeddedManifest) { self.manifestBlock = manifestBlock; self.successBlock = success; self.errorBlock = error; [self startLoadingFromManifest:embeddedManifest]; } else { error([NSError errorWithDomain:EXUpdatesEmbeddedAppLoaderErrorDomain code:1008 userInfo:@{NSLocalizedDescriptionKey: @"Failed to load embedded manifest. Make sure you have configured expo-updates correctly."}]); } } - (void)downloadAsset:(EXUpdatesAsset *)asset { NSURL *destinationUrl = [self.directory URLByAppendingPathComponent:asset.filename]; dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{ if ([[NSFileManager defaultManager] fileExistsAtPath:[destinationUrl path]]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self handleAssetDownloadAlreadyExists:asset]; }); } else { NSAssert(asset.mainBundleFilename, @"embedded asset mainBundleFilename must be nonnull"); NSString *bundlePath = asset.mainBundleDir ? [[NSBundle mainBundle] pathForResource:asset.mainBundleFilename ofType:asset.type inDirectory:asset.mainBundleDir] : [[NSBundle mainBundle] pathForResource:asset.mainBundleFilename ofType:asset.type]; NSAssert(bundlePath, @"NSBundle must contain the expected assets"); if (!bundlePath) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Could not find the expected embedded asset %@.%@. Check that expo-updates is installed correctly.", asset.mainBundleFilename, asset.type] userInfo:nil]; } NSError *err; if ([[NSFileManager defaultManager] copyItemAtPath:bundlePath toPath:[destinationUrl path] error:&err]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self handleAssetDownloadWithData:[NSData dataWithContentsOfFile:bundlePath] response:nil asset:asset]; }); } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self handleAssetDownloadWithError:err asset:asset]; }); } } }); } - (void)loadUpdateFromUrl:(NSURL *)url success:(EXUpdatesAppLoaderSuccessBlock)success error:(EXUpdatesAppLoaderErrorBlock)error { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Should not call EXUpdatesEmbeddedAppLoader#loadUpdateFromUrl" userInfo:nil]; } @end NS_ASSUME_NONNULL_END