yeet
This commit is contained in:
286
node_modules/expo-updates/ios/EXUpdates/EXUpdatesAppController.m
generated
vendored
Normal file
286
node_modules/expo-updates/ios/EXUpdates/EXUpdatesAppController.m
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppController.h>
|
||||
#import <EXUpdates/EXUpdatesAppLauncher.h>
|
||||
#import <EXUpdates/EXUpdatesAppLauncherNoDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesAppLauncherWithDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesReaper.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicyNewest.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesAppControllerErrorDomain = @"EXUpdatesAppController";
|
||||
|
||||
static NSString * const EXUpdatesConfigPlistName = @"Expo";
|
||||
|
||||
static NSString * const EXUpdatesUpdateAvailableEventName = @"updateAvailable";
|
||||
static NSString * const EXUpdatesNoUpdateAvailableEventName = @"noUpdateAvailable";
|
||||
static NSString * const EXUpdatesErrorEventName = @"error";
|
||||
|
||||
@interface EXUpdatesAppController ()
|
||||
|
||||
@property (nonatomic, readwrite, strong) EXUpdatesConfig *config;
|
||||
@property (nonatomic, readwrite, strong) id<EXUpdatesAppLauncher> launcher;
|
||||
@property (nonatomic, readwrite, strong) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, readwrite, strong) id<EXUpdatesSelectionPolicy> selectionPolicy;
|
||||
@property (nonatomic, readwrite, strong) dispatch_queue_t assetFilesQueue;
|
||||
|
||||
@property (nonatomic, readwrite, strong) NSURL *updatesDirectory;
|
||||
|
||||
@property (nonatomic, strong) id<EXUpdatesAppLauncher> candidateLauncher;
|
||||
@property (nonatomic, assign) BOOL hasLaunched;
|
||||
@property (nonatomic, strong) dispatch_queue_t controllerQueue;
|
||||
|
||||
@property (nonatomic, assign) BOOL isStarted;
|
||||
@property (nonatomic, assign) BOOL isEmergencyLaunch;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesAppController
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static EXUpdatesAppController *theController;
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
if (!theController) {
|
||||
theController = [[EXUpdatesAppController alloc] init];
|
||||
}
|
||||
});
|
||||
return theController;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_config = [self _loadConfigFromExpoPlist];
|
||||
_database = [[EXUpdatesDatabase alloc] init];
|
||||
_selectionPolicy = [[EXUpdatesSelectionPolicyNewest alloc] initWithRuntimeVersion:[EXUpdatesUtils getRuntimeVersionWithConfig:_config]];
|
||||
_assetFilesQueue = dispatch_queue_create("expo.controller.AssetFilesQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_controllerQueue = dispatch_queue_create("expo.controller.ControllerQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_isStarted = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setConfiguration:(NSDictionary *)configuration
|
||||
{
|
||||
if (_updatesDirectory) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:@"EXUpdatesAppController:setConfiguration should not be called after start"
|
||||
userInfo:@{}];
|
||||
}
|
||||
[_config loadConfigFromDictionary:configuration];
|
||||
_selectionPolicy = [[EXUpdatesSelectionPolicyNewest alloc] initWithRuntimeVersion:[EXUpdatesUtils getRuntimeVersionWithConfig:_config]];
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
NSAssert(!_updatesDirectory, @"EXUpdatesAppController:start should only be called once per instance");
|
||||
|
||||
if (!_config.isEnabled) {
|
||||
EXUpdatesAppLauncherNoDatabase *launcher = [[EXUpdatesAppLauncherNoDatabase alloc] init];
|
||||
_launcher = launcher;
|
||||
[launcher launchUpdateWithConfig:_config];
|
||||
|
||||
if (_delegate) {
|
||||
[_delegate appController:self didStartWithSuccess:self.launchAssetUrl != nil];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_config.updateUrl) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:@"expo-updates is enabled, but no valid URL is configured under EXUpdatesURL. If you are making a release build for the first time, make sure you have run `expo publish` at least once."
|
||||
userInfo:@{}];
|
||||
}
|
||||
|
||||
_isStarted = YES;
|
||||
|
||||
NSError *fsError;
|
||||
_updatesDirectory = [EXUpdatesUtils initializeUpdatesDirectoryWithError:&fsError];
|
||||
if (fsError) {
|
||||
[self _emergencyLaunchWithFatalError:fsError];
|
||||
return;
|
||||
}
|
||||
|
||||
__block BOOL dbSuccess;
|
||||
__block NSError *dbError;
|
||||
dispatch_semaphore_t dbSemaphore = dispatch_semaphore_create(0);
|
||||
dispatch_async(_database.databaseQueue, ^{
|
||||
dbSuccess = [self->_database openDatabaseInDirectory:self->_updatesDirectory withError:&dbError];
|
||||
dispatch_semaphore_signal(dbSemaphore);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(dbSemaphore, DISPATCH_TIME_FOREVER);
|
||||
if (!dbSuccess) {
|
||||
[self _emergencyLaunchWithFatalError:dbError];
|
||||
return;
|
||||
}
|
||||
|
||||
EXUpdatesAppLoaderTask *loaderTask = [[EXUpdatesAppLoaderTask alloc] initWithConfig:_config
|
||||
database:_database
|
||||
directory:_updatesDirectory
|
||||
selectionPolicy:_selectionPolicy
|
||||
delegateQueue:_controllerQueue];
|
||||
loaderTask.delegate = self;
|
||||
[loaderTask start];
|
||||
}
|
||||
|
||||
- (void)startAndShowLaunchScreen:(UIWindow *)window
|
||||
{
|
||||
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
NSString *launchScreen = (NSString *)[mainBundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"] ?: @"LaunchScreen";
|
||||
|
||||
if ([mainBundle pathForResource:launchScreen ofType:@"nib"] != nil) {
|
||||
NSArray *views = [mainBundle loadNibNamed:launchScreen owner:self options:nil];
|
||||
rootViewController.view = views.firstObject;
|
||||
rootViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
} else if ([mainBundle pathForResource:launchScreen ofType:@"storyboard"] != nil ||
|
||||
[mainBundle pathForResource:launchScreen ofType:@"storyboardc"] != nil) {
|
||||
UIStoryboard *launchScreenStoryboard = [UIStoryboard storyboardWithName:launchScreen bundle:nil];
|
||||
rootViewController = [launchScreenStoryboard instantiateInitialViewController];
|
||||
} else {
|
||||
NSLog(@"Launch screen could not be loaded from a .xib or .storyboard. Unexpected loading behavior may occur.");
|
||||
UIView *view = [UIView new];
|
||||
view.backgroundColor = [UIColor whiteColor];
|
||||
rootViewController.view = view;
|
||||
}
|
||||
|
||||
window.rootViewController = rootViewController;
|
||||
[window makeKeyAndVisible];
|
||||
|
||||
[self start];
|
||||
}
|
||||
|
||||
- (void)requestRelaunchWithCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion
|
||||
{
|
||||
if (_bridge) {
|
||||
EXUpdatesAppLauncherWithDatabase *launcher = [[EXUpdatesAppLauncherWithDatabase alloc] initWithConfig:_config database:_database directory:_updatesDirectory completionQueue:_controllerQueue];
|
||||
_candidateLauncher = launcher;
|
||||
[launcher launchUpdateWithSelectionPolicy:self->_selectionPolicy completion:^(NSError * _Nullable error, BOOL success) {
|
||||
if (success) {
|
||||
self->_launcher = self->_candidateLauncher;
|
||||
completion(YES);
|
||||
[self->_bridge reload];
|
||||
[self _runReaper];
|
||||
} else {
|
||||
NSLog(@"Failed to relaunch: %@", error.localizedDescription);
|
||||
completion(NO);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
NSLog(@"EXUpdatesAppController: Failed to reload because bridge was nil. Did you set the bridge property on the controller singleton?");
|
||||
completion(NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable EXUpdatesUpdate *)launchedUpdate
|
||||
{
|
||||
return _launcher.launchedUpdate ?: nil;
|
||||
}
|
||||
|
||||
- (nullable NSURL *)launchAssetUrl
|
||||
{
|
||||
return _launcher.launchAssetUrl ?: nil;
|
||||
}
|
||||
|
||||
- (nullable NSDictionary *)assetFilesMap
|
||||
{
|
||||
return _launcher.assetFilesMap ?: nil;
|
||||
}
|
||||
|
||||
- (BOOL)isUsingEmbeddedAssets
|
||||
{
|
||||
if (!_launcher) {
|
||||
return YES;
|
||||
}
|
||||
return _launcher.isUsingEmbeddedAssets;
|
||||
}
|
||||
|
||||
# pragma mark - EXUpdatesAppLoaderTaskDelegate
|
||||
|
||||
- (BOOL)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didLoadCachedUpdate:(nonnull EXUpdatesUpdate *)update
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didStartLoadingUpdate:(EXUpdatesUpdate *)update
|
||||
{
|
||||
// do nothing here for now
|
||||
}
|
||||
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithLauncher:(id<EXUpdatesAppLauncher>)launcher isUpToDate:(BOOL)isUpToDate
|
||||
{
|
||||
_launcher = launcher;
|
||||
if (self->_delegate) {
|
||||
[EXUpdatesUtils runBlockOnMainThread:^{
|
||||
[self->_delegate appController:self didStartWithSuccess:YES];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithError:(NSError *)error
|
||||
{
|
||||
[self _emergencyLaunchWithFatalError:error];
|
||||
}
|
||||
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishBackgroundUpdateWithStatus:(EXUpdatesBackgroundUpdateStatus)status update:(nullable EXUpdatesUpdate *)update error:(nullable NSError *)error
|
||||
{
|
||||
if (status == EXUpdatesBackgroundUpdateStatusError) {
|
||||
NSAssert(error != nil, @"Background update with error status must have a nonnull error object");
|
||||
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesErrorEventName body:@{@"message": error.localizedDescription}];
|
||||
} else if (status == EXUpdatesBackgroundUpdateStatusUpdateAvailable) {
|
||||
NSAssert(update != nil, @"Background update with error status must have a nonnull update object");
|
||||
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesUpdateAvailableEventName body:@{@"manifest": update.rawManifest}];
|
||||
} else if (status == EXUpdatesBackgroundUpdateStatusNoUpdateAvailable) {
|
||||
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesNoUpdateAvailableEventName body:@{}];
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - internal
|
||||
|
||||
- (EXUpdatesConfig *)_loadConfigFromExpoPlist
|
||||
{
|
||||
NSString *configPath = [[NSBundle mainBundle] pathForResource:EXUpdatesConfigPlistName ofType:@"plist"];
|
||||
if (!configPath) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:@"Cannot load configuration from Expo.plist. Please ensure you've followed the setup and installation instructions for expo-updates to create Expo.plist and add it to your Xcode project."
|
||||
userInfo:@{}];
|
||||
}
|
||||
|
||||
return [EXUpdatesConfig configWithDictionary:[NSDictionary dictionaryWithContentsOfFile:configPath]];
|
||||
}
|
||||
|
||||
- (void)_runReaper
|
||||
{
|
||||
if (_launcher.launchedUpdate) {
|
||||
[EXUpdatesReaper reapUnusedUpdatesWithConfig:_config
|
||||
database:_database
|
||||
directory:_updatesDirectory
|
||||
selectionPolicy:_selectionPolicy
|
||||
launchedUpdate:_launcher.launchedUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_emergencyLaunchWithFatalError:(NSError *)error
|
||||
{
|
||||
_isEmergencyLaunch = YES;
|
||||
|
||||
EXUpdatesAppLauncherNoDatabase *launcher = [[EXUpdatesAppLauncherNoDatabase alloc] init];
|
||||
_launcher = launcher;
|
||||
[launcher launchUpdateWithConfig:_config fatalError:error];
|
||||
|
||||
if (_delegate) {
|
||||
[EXUpdatesUtils runBlockOnMainThread:^{
|
||||
[self->_delegate appController:self didStartWithSuccess:self.launchAssetUrl != nil];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
Reference in New Issue
Block a user