yeet
This commit is contained in:
16
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncher.h
generated
vendored
Normal file
16
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncher.h
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol EXUpdatesAppLauncher
|
||||
|
||||
@property (nullable, nonatomic, strong, readonly) EXUpdatesUpdate *launchedUpdate;
|
||||
@property (nullable, nonatomic, strong, readonly) NSURL *launchAssetUrl;
|
||||
@property (nullable, nonatomic, strong, readonly) NSDictionary *assetFilesMap;
|
||||
@property (nonatomic, assign, readonly) BOOL isUsingEmbeddedAssets;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
17
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.h
generated
vendored
Normal file
17
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.h
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncher.h>
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesAppLauncherNoDatabase : NSObject <EXUpdatesAppLauncher>
|
||||
|
||||
- (void)launchUpdateWithConfig:(EXUpdatesConfig *)config;
|
||||
- (void)launchUpdateWithConfig:(EXUpdatesConfig *)config fatalError:(NSError *)error;
|
||||
+ (nullable NSString *)consumeError;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
109
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.m
generated
vendored
Normal file
109
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.m
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesAppLauncherNoDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesErrorLogFile = @"expo-error.log";
|
||||
|
||||
@interface EXUpdatesAppLauncherNoDatabase ()
|
||||
|
||||
@property (nullable, nonatomic, strong, readwrite) EXUpdatesUpdate *launchedUpdate;
|
||||
@property (nullable, nonatomic, strong, readwrite) NSURL *launchAssetUrl;
|
||||
@property (nullable, nonatomic, strong, readwrite) NSMutableDictionary *assetFilesMap;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesAppLauncherNoDatabase
|
||||
|
||||
- (void)launchUpdateWithConfig:(EXUpdatesConfig *)config
|
||||
{
|
||||
_launchedUpdate = [EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:config database:nil];
|
||||
if (_launchedUpdate) {
|
||||
if (_launchedUpdate.status == EXUpdatesUpdateStatusEmbedded) {
|
||||
NSAssert(_assetFilesMap == nil, @"assetFilesMap should be null for embedded updates");
|
||||
_launchAssetUrl = [[NSBundle mainBundle] URLForResource:EXUpdatesBareEmbeddedBundleFilename withExtension:EXUpdatesBareEmbeddedBundleFileType];
|
||||
} else {
|
||||
_launchAssetUrl = [[NSBundle mainBundle] URLForResource:EXUpdatesEmbeddedBundleFilename withExtension:EXUpdatesEmbeddedBundleFileType];
|
||||
|
||||
NSMutableDictionary *assetFilesMap = [NSMutableDictionary new];
|
||||
for (EXUpdatesAsset *asset in _launchedUpdate.assets) {
|
||||
NSURL *localUrl = [[NSBundle mainBundle] URLForResource:asset.mainBundleFilename withExtension:asset.type];
|
||||
if (localUrl && asset.key) {
|
||||
assetFilesMap[asset.key] = localUrl.absoluteString;
|
||||
}
|
||||
}
|
||||
_assetFilesMap = assetFilesMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isUsingEmbeddedAssets
|
||||
{
|
||||
return _assetFilesMap == nil;
|
||||
}
|
||||
|
||||
- (void)launchUpdateWithConfig:(EXUpdatesConfig *)config fatalError:(NSError *)error;
|
||||
{
|
||||
[self launchUpdateWithConfig:config];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self _writeErrorToLog:error];
|
||||
});
|
||||
}
|
||||
|
||||
+ (nullable NSString *)consumeError;
|
||||
{
|
||||
NSString *errorLogFilePath = [[self class] _errorLogFilePath];
|
||||
NSData *data = [NSData dataWithContentsOfFile:errorLogFilePath options:kNilOptions error:nil];
|
||||
if (data) {
|
||||
NSError *err;
|
||||
if (![NSFileManager.defaultManager removeItemAtPath:errorLogFilePath error:&err]) {
|
||||
NSLog(@"Could not delete error log: %@", err.localizedDescription);
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_writeErrorToLog:(NSError *)error
|
||||
{
|
||||
NSString *serializedError = [NSString stringWithFormat:@"Expo encountered a fatal error: %@", [self _serializeError:error]];
|
||||
NSData *data = [serializedError dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSError *err;
|
||||
if (![data writeToFile:[[self class] _errorLogFilePath] options:NSDataWritingAtomic error:&err]) {
|
||||
NSLog(@"Could not write fatal error to log: %@", error.localizedDescription);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)_serializeError:(NSError *)error
|
||||
{
|
||||
NSString *localizedFailureReason = error.localizedFailureReason;
|
||||
NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
|
||||
|
||||
NSMutableString *serialization = [[NSString stringWithFormat:@"Time: %f\nDomain: %@\nCode: %li\nDescription: %@",
|
||||
[[NSDate date] timeIntervalSince1970] * 1000,
|
||||
error.domain,
|
||||
(long)error.code,
|
||||
error.localizedDescription] mutableCopy];
|
||||
if (localizedFailureReason) {
|
||||
[serialization appendFormat:@"\nFailure Reason: %@", localizedFailureReason];
|
||||
}
|
||||
if (underlyingError) {
|
||||
[serialization appendFormat:@"\n\nUnderlying Error:\n%@", [self _serializeError:underlyingError]];
|
||||
}
|
||||
return serialization;
|
||||
}
|
||||
|
||||
+ (NSString *)_errorLogFilePath
|
||||
{
|
||||
NSURL *applicationDocumentsDirectory = [[NSFileManager.defaultManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
return [[applicationDocumentsDirectory URLByAppendingPathComponent:EXUpdatesErrorLogFile] path];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
29
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.h
generated
vendored
Normal file
29
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.h
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncher.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^EXUpdatesAppLauncherCompletionBlock)(NSError * _Nullable error, BOOL success);
|
||||
typedef void (^EXUpdatesAppLauncherUpdateCompletionBlock)(NSError * _Nullable error, EXUpdatesUpdate * _Nullable launchableUpdate);
|
||||
|
||||
@interface EXUpdatesAppLauncherWithDatabase : NSObject <EXUpdatesAppLauncher>
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
completionQueue:(dispatch_queue_t)completionQueue;
|
||||
|
||||
- (void)launchUpdateWithSelectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
completion:(EXUpdatesAppLauncherCompletionBlock)completion;
|
||||
|
||||
+ (void)launchableUpdateWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
completion:(EXUpdatesAppLauncherUpdateCompletionBlock)completion
|
||||
completionQueue:(dispatch_queue_t)completionQueue;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
288
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.m
generated
vendored
Normal file
288
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.m
generated
vendored
Normal file
@ -0,0 +1,288 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncherWithDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesAppLauncherWithDatabase ()
|
||||
|
||||
@property (nullable, nonatomic, strong, readwrite) EXUpdatesUpdate *launchedUpdate;
|
||||
@property (nullable, nonatomic, strong, readwrite) NSURL *launchAssetUrl;
|
||||
@property (nullable, nonatomic, strong, readwrite) NSMutableDictionary *assetFilesMap;
|
||||
|
||||
@property (nonatomic, strong) EXUpdatesConfig *config;
|
||||
@property (nonatomic, strong) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, strong) NSURL *directory;
|
||||
@property (nonatomic, strong) EXUpdatesFileDownloader *downloader;
|
||||
@property (nonatomic, copy) EXUpdatesAppLauncherCompletionBlock completion;
|
||||
@property (nonatomic, strong) dispatch_queue_t completionQueue;
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t launcherQueue;
|
||||
@property (nonatomic, assign) NSUInteger completedAssets;
|
||||
|
||||
@property (nonatomic, strong) NSError *launchAssetError;
|
||||
|
||||
@end
|
||||
|
||||
static NSString * const EXUpdatesAppLauncherErrorDomain = @"AppLauncher";
|
||||
|
||||
@implementation EXUpdatesAppLauncherWithDatabase
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
completionQueue:(dispatch_queue_t)completionQueue
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_launcherQueue = dispatch_queue_create("expo.launcher.LauncherQueue", DISPATCH_QUEUE_SERIAL);
|
||||
_completedAssets = 0;
|
||||
_config = config;
|
||||
_database = database;
|
||||
_directory = directory;
|
||||
_completionQueue = completionQueue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)launchableUpdateWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
completion:(EXUpdatesAppLauncherUpdateCompletionBlock)completion
|
||||
completionQueue:(dispatch_queue_t)completionQueue
|
||||
{
|
||||
dispatch_async(database.databaseQueue, ^{
|
||||
NSError *error;
|
||||
NSArray<EXUpdatesUpdate *> *launchableUpdates = [database launchableUpdatesWithConfig:config error:&error];
|
||||
dispatch_async(completionQueue, ^{
|
||||
if (!launchableUpdates) {
|
||||
completion(error, nil);
|
||||
}
|
||||
|
||||
// We can only run an update marked as embedded if it's actually the update embedded in the
|
||||
// current binary. We might have an older update from a previous binary still listed in the
|
||||
// database with Embedded status so we need to filter that out here.
|
||||
EXUpdatesUpdate *embeddedManifest = [EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:config database:database];
|
||||
NSMutableArray<EXUpdatesUpdate *>*filteredLaunchableUpdates = [NSMutableArray new];
|
||||
for (EXUpdatesUpdate *update in launchableUpdates) {
|
||||
if (update.status == EXUpdatesUpdateStatusEmbedded) {
|
||||
if (embeddedManifest && ![update.updateId isEqual:embeddedManifest.updateId]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
[filteredLaunchableUpdates addObject:update];
|
||||
}
|
||||
|
||||
completion(nil, [selectionPolicy launchableUpdateWithUpdates:filteredLaunchableUpdates]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)launchUpdateWithSelectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
completion:(EXUpdatesAppLauncherCompletionBlock)completion
|
||||
{
|
||||
NSAssert(!_completion, @"EXUpdatesAppLauncher:launchUpdateWithSelectionPolicy:successBlock should not be called twice on the same instance");
|
||||
_completion = completion;
|
||||
|
||||
if (!_launchedUpdate) {
|
||||
[[self class] launchableUpdateWithConfig:_config database:_database selectionPolicy:selectionPolicy completion:^(NSError * _Nullable error, EXUpdatesUpdate * _Nullable launchableUpdate) {
|
||||
if (error || !launchableUpdate) {
|
||||
if (self->_completion) {
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary new];
|
||||
userInfo[NSLocalizedDescriptionKey] = @"No launchable updates found in database";
|
||||
if (error) {
|
||||
userInfo[NSUnderlyingErrorKey] = error;
|
||||
}
|
||||
self->_completion([NSError errorWithDomain:EXUpdatesAppLauncherErrorDomain code:1011 userInfo:userInfo], NO);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self->_launchedUpdate = launchableUpdate;
|
||||
[self _ensureAllAssetsExist];
|
||||
}
|
||||
} completionQueue:_launcherQueue];
|
||||
} else {
|
||||
[self _ensureAllAssetsExist];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isUsingEmbeddedAssets
|
||||
{
|
||||
return _assetFilesMap == nil;
|
||||
}
|
||||
|
||||
- (void)_ensureAllAssetsExist
|
||||
{
|
||||
if (_launchedUpdate.status == EXUpdatesUpdateStatusEmbedded) {
|
||||
NSAssert(_assetFilesMap == nil, @"assetFilesMap should be null for embedded updates");
|
||||
_launchAssetUrl = [[NSBundle mainBundle] URLForResource:EXUpdatesBareEmbeddedBundleFilename withExtension:EXUpdatesBareEmbeddedBundleFileType];
|
||||
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
self->_completion(self->_launchAssetError, self->_launchAssetUrl != nil);
|
||||
self->_completion = nil;
|
||||
});
|
||||
return;
|
||||
} else if (_launchedUpdate.status == EXUpdatesUpdateStatusDevelopment) {
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
self->_completion(nil, YES);
|
||||
self->_completion = nil;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_assetFilesMap = [NSMutableDictionary new];
|
||||
|
||||
if (_launchedUpdate) {
|
||||
NSUInteger totalAssetCount = _launchedUpdate.assets.count;
|
||||
for (EXUpdatesAsset *asset in _launchedUpdate.assets) {
|
||||
NSURL *assetLocalUrl = [_directory URLByAppendingPathComponent:asset.filename];
|
||||
[self _ensureAssetExists:asset withLocalUrl:assetLocalUrl completion:^(BOOL exists) {
|
||||
dispatch_assert_queue(self->_launcherQueue);
|
||||
self->_completedAssets++;
|
||||
|
||||
if (exists) {
|
||||
if (asset.isLaunchAsset) {
|
||||
self->_launchAssetUrl = assetLocalUrl;
|
||||
} else {
|
||||
if (asset.key) {
|
||||
self->_assetFilesMap[asset.key] = assetLocalUrl.absoluteString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->_completedAssets == totalAssetCount) {
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
self->_completion(self->_launchAssetError, self->_launchAssetUrl != nil);
|
||||
self->_completion = nil;
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_ensureAssetExists:(EXUpdatesAsset *)asset withLocalUrl:(NSURL *)assetLocalUrl completion:(void (^)(BOOL exists))completion
|
||||
{
|
||||
[self _checkExistenceOfAsset:asset withLocalUrl:assetLocalUrl completion:^(BOOL exists) {
|
||||
if (exists) {
|
||||
completion(YES);
|
||||
return;
|
||||
}
|
||||
|
||||
[self _maybeCopyAssetFromMainBundle:asset withLocalUrl:assetLocalUrl completion:^(BOOL success, NSError * _Nullable error) {
|
||||
if (success) {
|
||||
completion(YES);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
NSLog(@"Error copying embedded asset %@: %@", asset.key, error.localizedDescription);
|
||||
}
|
||||
|
||||
[self _downloadAsset:asset withLocalUrl:assetLocalUrl completion:^(NSError * _Nullable error, EXUpdatesAsset *asset, NSURL *assetLocalUrl) {
|
||||
if (error) {
|
||||
if (asset.isLaunchAsset) {
|
||||
// save the error -- since this is the launch asset, the launcher will fail
|
||||
// so we want to propagate this error
|
||||
self->_launchAssetError = error;
|
||||
}
|
||||
NSLog(@"Failed to load missing asset %@: %@", asset.key, error.localizedDescription);
|
||||
completion(NO);
|
||||
} else {
|
||||
// attempt to update the database record to match the newly downloaded asset
|
||||
// but don't block launching on this
|
||||
dispatch_async(self->_database.databaseQueue, ^{
|
||||
NSError *error;
|
||||
[self->_database updateAsset:asset error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Could not write data for downloaded asset to database: %@", error.localizedDescription);
|
||||
}
|
||||
});
|
||||
|
||||
completion(YES);
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_checkExistenceOfAsset:(EXUpdatesAsset *)asset withLocalUrl:(NSURL *)assetLocalUrl completion:(void (^)(BOOL exists))completion
|
||||
{
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:[assetLocalUrl path]];
|
||||
dispatch_async(self->_launcherQueue, ^{
|
||||
completion(exists);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_maybeCopyAssetFromMainBundle:(EXUpdatesAsset *)asset
|
||||
withLocalUrl:(NSURL *)assetLocalUrl
|
||||
completion:(void (^)(BOOL success, NSError * _Nullable error))completion
|
||||
{
|
||||
EXUpdatesUpdate *embeddedManifest = [EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:_config database:_database];
|
||||
if (embeddedManifest) {
|
||||
EXUpdatesAsset *matchingAsset;
|
||||
for (EXUpdatesAsset *embeddedAsset in embeddedManifest.assets) {
|
||||
if ([embeddedAsset.key isEqualToString:asset.key]) {
|
||||
matchingAsset = embeddedAsset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingAsset && matchingAsset.mainBundleFilename) {
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:matchingAsset.mainBundleFilename ofType:matchingAsset.type];
|
||||
NSError *error;
|
||||
BOOL success = [NSFileManager.defaultManager copyItemAtPath:bundlePath toPath:[assetLocalUrl path] error:&error];
|
||||
dispatch_async(self->_launcherQueue, ^{
|
||||
completion(success, error);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completion(NO, nil);
|
||||
}
|
||||
|
||||
- (void)_downloadAsset:(EXUpdatesAsset *)asset
|
||||
withLocalUrl:(NSURL *)assetLocalUrl
|
||||
completion:(void (^)(NSError * _Nullable error, EXUpdatesAsset *asset, NSURL *assetLocalUrl))completion
|
||||
{
|
||||
if (!asset.url) {
|
||||
completion([NSError errorWithDomain:EXUpdatesAppLauncherErrorDomain code:1007 userInfo:@{NSLocalizedDescriptionKey: @"Failed to download asset with no URL provided"}], asset, assetLocalUrl);
|
||||
}
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
[self.downloader downloadFileFromURL:asset.url toPath:[assetLocalUrl path] successBlock:^(NSData *data, NSURLResponse *response) {
|
||||
dispatch_async(self->_launcherQueue, ^{
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
asset.headers = ((NSHTTPURLResponse *)response).allHeaderFields;
|
||||
}
|
||||
asset.contentHash = [EXUpdatesUtils sha256WithData:data];
|
||||
asset.downloadTime = [NSDate date];
|
||||
completion(nil, asset, assetLocalUrl);
|
||||
});
|
||||
} errorBlock:^(NSError *error, NSURLResponse *response) {
|
||||
dispatch_async(self->_launcherQueue, ^{
|
||||
completion(error, asset, assetLocalUrl);
|
||||
});
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (EXUpdatesFileDownloader *)downloader
|
||||
{
|
||||
if (!_downloader) {
|
||||
_downloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:_config];
|
||||
}
|
||||
return _downloader;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
15
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicy.h
generated
vendored
Normal file
15
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicy.h
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol EXUpdatesSelectionPolicy
|
||||
|
||||
- (nullable EXUpdatesUpdate *)launchableUpdateWithUpdates:(NSArray<EXUpdatesUpdate *> *)updates;
|
||||
- (NSArray<EXUpdatesUpdate *> *)updatesToDeleteWithLaunchedUpdate:(EXUpdatesUpdate *)launchedUpdate updates:(NSArray<EXUpdatesUpdate *> *)updates;
|
||||
- (BOOL)shouldLoadNewUpdate:(nullable EXUpdatesUpdate *)newUpdate withLaunchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
14
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicyNewest.h
generated
vendored
Normal file
14
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicyNewest.h
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesSelectionPolicyNewest : NSObject <EXUpdatesSelectionPolicy>
|
||||
|
||||
- (instancetype)initWithRuntimeVersion:(NSString *)runtimeVersion;
|
||||
- (instancetype)initWithRuntimeVersions:(NSArray<NSString *> *)runtimeVersions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
83
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicyNewest.m
generated
vendored
Normal file
83
node_modules/expo-updates/ios/EXUpdates/AppLauncher/EXUpdatesSelectionPolicyNewest.m
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicyNewest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesSelectionPolicyNewest ()
|
||||
|
||||
@property (nonatomic, strong) NSArray<NSString *> *runtimeVersions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesSelectionPolicyNewest
|
||||
|
||||
- (instancetype)initWithRuntimeVersions:(NSArray<NSString *> *)runtimeVersions
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_runtimeVersions = runtimeVersions;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRuntimeVersion:(NSString *)runtimeVersion
|
||||
{
|
||||
return [self initWithRuntimeVersions:@[runtimeVersion]];
|
||||
}
|
||||
|
||||
- (nullable EXUpdatesUpdate *)launchableUpdateWithUpdates:(NSArray<EXUpdatesUpdate *> *)updates
|
||||
{
|
||||
EXUpdatesUpdate *runnableUpdate;
|
||||
NSDate *runnableUpdateCommitTime;
|
||||
for (EXUpdatesUpdate *update in updates) {
|
||||
if (![_runtimeVersions containsObject:update.runtimeVersion]) {
|
||||
continue;
|
||||
}
|
||||
NSDate *commitTime = update.commitTime;
|
||||
if (!runnableUpdateCommitTime || [runnableUpdateCommitTime compare:commitTime] == NSOrderedAscending) {
|
||||
runnableUpdate = update;
|
||||
runnableUpdateCommitTime = commitTime;
|
||||
}
|
||||
}
|
||||
return runnableUpdate;
|
||||
}
|
||||
|
||||
- (NSArray<EXUpdatesUpdate *> *)updatesToDeleteWithLaunchedUpdate:(EXUpdatesUpdate *)launchedUpdate updates:(NSArray<EXUpdatesUpdate *> *)updates
|
||||
{
|
||||
if (!launchedUpdate) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray<EXUpdatesUpdate *> *updatesToDelete = [NSMutableArray new];
|
||||
// keep the launched update and one other, the next newest, to be safe and make rollbacks faster
|
||||
EXUpdatesUpdate *nextNewestUpdate;
|
||||
for (EXUpdatesUpdate *update in updates) {
|
||||
if ([launchedUpdate.commitTime compare:update.commitTime] == NSOrderedDescending) {
|
||||
[updatesToDelete addObject:update];
|
||||
if (!nextNewestUpdate || [update.commitTime compare:nextNewestUpdate.commitTime] == NSOrderedDescending) {
|
||||
nextNewestUpdate = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextNewestUpdate) {
|
||||
[updatesToDelete removeObject:nextNewestUpdate];
|
||||
}
|
||||
return updatesToDelete;
|
||||
}
|
||||
|
||||
- (BOOL)shouldLoadNewUpdate:(nullable EXUpdatesUpdate *)newUpdate withLaunchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
|
||||
{
|
||||
if (!newUpdate) {
|
||||
return false;
|
||||
}
|
||||
if (!launchedUpdate) {
|
||||
return true;
|
||||
}
|
||||
return [launchedUpdate.commitTime compare:newUpdate.commitTime] == NSOrderedAscending;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
29
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader+Private.h
generated
vendored
Normal file
29
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader+Private.h
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesAppLoader ()
|
||||
|
||||
@property (nonatomic, strong) EXUpdatesConfig *config;
|
||||
@property (nonatomic, strong) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, strong) NSURL *directory;
|
||||
@property (nonatomic, strong) EXUpdatesUpdate *updateManifest;
|
||||
@property (nonatomic, copy) EXUpdatesAppLoaderManifestBlock manifestBlock;
|
||||
@property (nonatomic, copy) EXUpdatesAppLoaderSuccessBlock successBlock;
|
||||
@property (nonatomic, copy) EXUpdatesAppLoaderErrorBlock errorBlock;
|
||||
|
||||
- (void)startLoadingFromManifest:(EXUpdatesUpdate *)updateManifest;
|
||||
- (void)handleAssetDownloadAlreadyExists:(EXUpdatesAsset *)asset;
|
||||
- (void)handleAssetDownloadWithData:(NSData *)data response:(nullable NSURLResponse *)response asset:(EXUpdatesAsset *)asset;
|
||||
- (void)handleAssetDownloadWithError:(NSError *)error asset:(EXUpdatesAsset *)asset;
|
||||
|
||||
- (void)downloadAsset:(EXUpdatesAsset *)asset;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
35
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.h
generated
vendored
Normal file
35
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.h
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef BOOL (^EXUpdatesAppLoaderManifestBlock)(EXUpdatesUpdate *update);
|
||||
typedef void (^EXUpdatesAppLoaderSuccessBlock)(EXUpdatesUpdate * _Nullable update);
|
||||
typedef void (^EXUpdatesAppLoaderErrorBlock)(NSError *error);
|
||||
|
||||
@interface EXUpdatesAppLoader : NSObject
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
completionQueue:(dispatch_queue_t)completionQueue;
|
||||
|
||||
/**
|
||||
* Load an update from the given URL, which should respond with a valid manifest.
|
||||
*
|
||||
* The `onManifest` block is called as soon as the manifest has been downloaded.
|
||||
* The block should determine whether or not the update described by this manifest
|
||||
* should be downloaded, based on (for example) whether or not it already has the
|
||||
* update downloaded locally, and return the corresponding BOOL value.
|
||||
*/
|
||||
- (void)loadUpdateFromUrl:(NSURL *)url
|
||||
onManifest:(EXUpdatesAppLoaderManifestBlock)manifestBlock
|
||||
success:(EXUpdatesAppLoaderSuccessBlock)success
|
||||
error:(EXUpdatesAppLoaderErrorBlock)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
325
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.m
generated
vendored
Normal file
325
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.m
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLoader+Private.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
#import <UMCore/UMUtilities.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesAppLoader ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray<EXUpdatesAsset *> *assetsToLoad;
|
||||
@property (nonatomic, strong) NSMutableArray<EXUpdatesAsset *> *erroredAssets;
|
||||
@property (nonatomic, strong) NSMutableArray<EXUpdatesAsset *> *finishedAssets;
|
||||
@property (nonatomic, strong) NSMutableArray<EXUpdatesAsset *> *existingAssets;
|
||||
|
||||
@property (nonatomic, strong) NSLock *arrayLock;
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t completionQueue;
|
||||
|
||||
@end
|
||||
|
||||
static NSString * const EXUpdatesAppLoaderErrorDomain = @"EXUpdatesAppLoader";
|
||||
|
||||
@implementation EXUpdatesAppLoader
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
completionQueue:(dispatch_queue_t)completionQueue
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_assetsToLoad = [NSMutableArray new];
|
||||
_erroredAssets = [NSMutableArray new];
|
||||
_finishedAssets = [NSMutableArray new];
|
||||
_existingAssets = [NSMutableArray new];
|
||||
_arrayLock = [[NSLock alloc] init];
|
||||
_config = config;
|
||||
_database = database;
|
||||
_directory = directory;
|
||||
_completionQueue = completionQueue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_reset
|
||||
{
|
||||
_assetsToLoad = [NSMutableArray new];
|
||||
_erroredAssets = [NSMutableArray new];
|
||||
_finishedAssets = [NSMutableArray new];
|
||||
_existingAssets = [NSMutableArray new];
|
||||
_updateManifest = nil;
|
||||
_manifestBlock = nil;
|
||||
_successBlock = nil;
|
||||
_errorBlock = nil;
|
||||
}
|
||||
|
||||
# pragma mark - subclass methods
|
||||
|
||||
- (void)loadUpdateFromUrl:(NSURL *)url
|
||||
onManifest:(EXUpdatesAppLoaderManifestBlock)manifestBlock
|
||||
success:(EXUpdatesAppLoaderSuccessBlock)success
|
||||
error:(EXUpdatesAppLoaderErrorBlock)error
|
||||
{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Should not call EXUpdatesAppLoader#loadUpdate -- use a subclass instead" userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)downloadAsset:(EXUpdatesAsset *)asset
|
||||
{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Should not call EXUpdatesAppLoader#loadUpdate -- use a subclass instead" userInfo:nil];
|
||||
}
|
||||
|
||||
# pragma mark - loading and database logic
|
||||
|
||||
- (void)startLoadingFromManifest:(EXUpdatesUpdate *)updateManifest
|
||||
{
|
||||
if (![self _shouldStartLoadingUpdate:updateManifest]) {
|
||||
if (_successBlock) {
|
||||
_successBlock(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateManifest.isDevelopmentMode) {
|
||||
dispatch_async(_database.databaseQueue, ^{
|
||||
NSError *updateError;
|
||||
[self->_database addUpdate:updateManifest error:&updateError];
|
||||
|
||||
if (updateError) {
|
||||
[self _finishWithError:updateError];
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *updateReadyError;
|
||||
[self->_database markUpdateFinished:updateManifest error:&updateReadyError];
|
||||
if (updateReadyError) {
|
||||
[self _finishWithError:updateReadyError];
|
||||
return;
|
||||
}
|
||||
|
||||
EXUpdatesAppLoaderSuccessBlock successBlock;
|
||||
if (self->_successBlock) {
|
||||
successBlock = self->_successBlock;
|
||||
}
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
if (successBlock) {
|
||||
successBlock(updateManifest);
|
||||
}
|
||||
[self _reset];
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(_database.databaseQueue, ^{
|
||||
NSError *existingUpdateError;
|
||||
EXUpdatesUpdate *existingUpdate = [self->_database updateWithId:updateManifest.updateId config:self->_config error:&existingUpdateError];
|
||||
|
||||
// if something has gone wrong on the server and we have two updates with the same id
|
||||
// but different scope keys, we should try to launch something rather than show a cryptic
|
||||
// error to the user.
|
||||
if (existingUpdate && ![existingUpdate.scopeKey isEqualToString:updateManifest.scopeKey]) {
|
||||
NSError *setScopeKeyError;
|
||||
[self->_database setScopeKey:updateManifest.scopeKey onUpdate:existingUpdate error:&setScopeKeyError];
|
||||
|
||||
if (setScopeKeyError) {
|
||||
[self _finishWithError:setScopeKeyError];
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"EXUpdatesAppLoader: Loaded an update with the same ID but a different scopeKey than one we already have on disk. This is a server error. Overwriting the scopeKey and loading the existing update.");
|
||||
}
|
||||
|
||||
if (existingUpdate && existingUpdate.status == EXUpdatesUpdateStatusReady) {
|
||||
if (self->_successBlock) {
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
self->_successBlock(updateManifest);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingUpdate) {
|
||||
// we've already partially downloaded the update.
|
||||
// however, it's not ready, so we should try to download all the assets again.
|
||||
self->_updateManifest = updateManifest;
|
||||
} else {
|
||||
if (existingUpdateError) {
|
||||
NSLog(@"Failed to select old update from DB: %@", existingUpdateError.localizedDescription);
|
||||
}
|
||||
// no update already exists with this ID, so we need to insert it and download everything.
|
||||
self->_updateManifest = updateManifest;
|
||||
NSError *updateError;
|
||||
[self->_database addUpdate:self->_updateManifest error:&updateError];
|
||||
|
||||
if (updateError) {
|
||||
[self _finishWithError:updateError];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->_updateManifest.assets && self->_updateManifest.assets.count > 0) {
|
||||
self->_assetsToLoad = [self->_updateManifest.assets mutableCopy];
|
||||
|
||||
for (EXUpdatesAsset *asset in self->_updateManifest.assets) {
|
||||
// before downloading, check to see if we already have this asset in the database
|
||||
NSError *matchingAssetError;
|
||||
EXUpdatesAsset *matchingDbEntry = [self->_database assetWithKey:asset.key error:&matchingAssetError];
|
||||
|
||||
if (matchingAssetError || !matchingDbEntry || !matchingDbEntry.filename) {
|
||||
[self downloadAsset:asset];
|
||||
} else {
|
||||
NSError *mergeError;
|
||||
[self->_database mergeAsset:asset withExistingEntry:matchingDbEntry error:&mergeError];
|
||||
if (mergeError) {
|
||||
NSLog(@"Failed to merge asset with existing database entry: %@", mergeError.localizedDescription);
|
||||
}
|
||||
// make sure the file actually exists on disk
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
NSURL *urlOnDisk = [self->_directory URLByAppendingPathComponent:asset.filename];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:[urlOnDisk path]]) {
|
||||
// file already exists, we don't need to download it again
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self handleAssetDownloadAlreadyExists:asset];
|
||||
});
|
||||
} else {
|
||||
[self downloadAsset:asset];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[self _finish];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)handleAssetDownloadAlreadyExists:(EXUpdatesAsset *)asset
|
||||
{
|
||||
[_arrayLock lock];
|
||||
[self->_assetsToLoad removeObject:asset];
|
||||
[self->_existingAssets addObject:asset];
|
||||
if (![self->_assetsToLoad count]) {
|
||||
[self _finish];
|
||||
}
|
||||
[_arrayLock unlock];
|
||||
}
|
||||
|
||||
- (void)handleAssetDownloadWithError:(NSError *)error asset:(EXUpdatesAsset *)asset
|
||||
{
|
||||
// TODO: retry. for now log an error
|
||||
NSLog(@"error loading asset %@: %@", asset.key, error.localizedDescription);
|
||||
[_arrayLock lock];
|
||||
[self->_assetsToLoad removeObject:asset];
|
||||
[self->_erroredAssets addObject:asset];
|
||||
if (![self->_assetsToLoad count]) {
|
||||
[self _finish];
|
||||
}
|
||||
[_arrayLock unlock];
|
||||
}
|
||||
|
||||
- (void)handleAssetDownloadWithData:(NSData *)data response:(nullable NSURLResponse *)response asset:(EXUpdatesAsset *)asset
|
||||
{
|
||||
[_arrayLock lock];
|
||||
[self->_assetsToLoad removeObject:asset];
|
||||
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
asset.headers = ((NSHTTPURLResponse *)response).allHeaderFields;
|
||||
}
|
||||
asset.contentHash = [EXUpdatesUtils sha256WithData:data];
|
||||
asset.downloadTime = [NSDate date];
|
||||
[self->_finishedAssets addObject:asset];
|
||||
|
||||
if (![self->_assetsToLoad count]) {
|
||||
[self _finish];
|
||||
}
|
||||
[_arrayLock unlock];
|
||||
}
|
||||
|
||||
# pragma mark - internal
|
||||
|
||||
- (BOOL)_shouldStartLoadingUpdate:(EXUpdatesUpdate *)updateManifest
|
||||
{
|
||||
return _manifestBlock(updateManifest);
|
||||
}
|
||||
|
||||
- (void)_finishWithError:(NSError *)error
|
||||
{
|
||||
dispatch_async(_completionQueue, ^{
|
||||
if (self->_errorBlock) {
|
||||
self->_errorBlock(error);
|
||||
}
|
||||
[self _reset];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_finish
|
||||
{
|
||||
dispatch_async(_database.databaseQueue, ^{
|
||||
[self->_arrayLock lock];
|
||||
for (EXUpdatesAsset *existingAsset in self->_existingAssets) {
|
||||
NSError *error;
|
||||
BOOL existingAssetFound = [self->_database addExistingAsset:existingAsset toUpdateWithId:self->_updateManifest.updateId error:&error];
|
||||
if (!existingAssetFound) {
|
||||
// the database and filesystem have gotten out of sync
|
||||
// do our best to create a new entry for this file even though it already existed on disk
|
||||
NSData *contents = [NSData dataWithContentsOfURL:[self->_directory URLByAppendingPathComponent:existingAsset.filename]];
|
||||
existingAsset.contentHash = [EXUpdatesUtils sha256WithData:contents];
|
||||
existingAsset.downloadTime = [NSDate date];
|
||||
[self->_finishedAssets addObject:existingAsset];
|
||||
}
|
||||
if (error) {
|
||||
NSLog(@"Error searching for existing asset in DB: %@", error.localizedDescription);
|
||||
}
|
||||
}
|
||||
NSError *assetError;
|
||||
[self->_database addNewAssets:self->_finishedAssets toUpdateWithId:self->_updateManifest.updateId error:&assetError];
|
||||
if (assetError) {
|
||||
[self->_arrayLock unlock];
|
||||
[self _finishWithError:assetError];
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self->_erroredAssets count]) {
|
||||
NSError *updateReadyError;
|
||||
[self->_database markUpdateFinished:self->_updateManifest error:&updateReadyError];
|
||||
if (updateReadyError) {
|
||||
[self->_arrayLock unlock];
|
||||
[self _finishWithError:updateReadyError];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EXUpdatesAppLoaderSuccessBlock successBlock;
|
||||
EXUpdatesAppLoaderErrorBlock errorBlock;
|
||||
|
||||
if (self->_erroredAssets.count) {
|
||||
if (self->_errorBlock) {
|
||||
errorBlock = self->_errorBlock;
|
||||
}
|
||||
} else {
|
||||
if (self->_successBlock) {
|
||||
successBlock = self->_successBlock;
|
||||
}
|
||||
}
|
||||
|
||||
[self->_arrayLock unlock];
|
||||
|
||||
dispatch_async(self->_completionQueue, ^{
|
||||
if (errorBlock) {
|
||||
errorBlock([NSError errorWithDomain:EXUpdatesAppLoaderErrorDomain
|
||||
code:1012
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Failed to load all assets"}]);
|
||||
} else if (successBlock) {
|
||||
successBlock(self->_updateManifest);
|
||||
}
|
||||
[self _reset];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
50
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.h
generated
vendored
Normal file
50
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.h
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright © 2020 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncher.h>
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, EXUpdatesBackgroundUpdateStatus) {
|
||||
EXUpdatesBackgroundUpdateStatusError = 0,
|
||||
EXUpdatesBackgroundUpdateStatusNoUpdateAvailable = 1,
|
||||
EXUpdatesBackgroundUpdateStatusUpdateAvailable = 2
|
||||
};
|
||||
|
||||
@class EXUpdatesAppLoaderTask;
|
||||
|
||||
@protocol EXUpdatesAppLoaderTaskDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* This method gives the delegate a backdoor option to ignore the cached update and force
|
||||
* a remote load if it decides the cached update is not runnable. Returning NO from this
|
||||
* callback will force a remote load, overriding the timeout and configuration settings for
|
||||
* whether or not to check for a remote update. Returning YES from this callback will make
|
||||
* EXUpdatesAppLoaderTask proceed as usual.
|
||||
*/
|
||||
- (BOOL)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didLoadCachedUpdate:(EXUpdatesUpdate *)update;
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didStartLoadingUpdate:(EXUpdatesUpdate *)update;
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithLauncher:(id<EXUpdatesAppLauncher>)launcher isUpToDate:(BOOL)isUpToDate;
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithError:(NSError *)error;
|
||||
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishBackgroundUpdateWithStatus:(EXUpdatesBackgroundUpdateStatus)status update:(nullable EXUpdatesUpdate *)update error:(nullable NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface EXUpdatesAppLoaderTask : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<EXUpdatesAppLoaderTaskDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
|
||||
- (void)start;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
299
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.m
generated
vendored
Normal file
299
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.m
generated
vendored
Normal file
@ -0,0 +1,299 @@
|
||||
// Copyright © 2020 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncherWithDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesAppLoaderTask.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesReaper.h>
|
||||
#import <EXUpdates/EXUpdatesRemoteAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesAppLoaderTaskErrorDomain = @"EXUpdatesAppLoaderTask";
|
||||
|
||||
@interface EXUpdatesAppLoaderTask ()
|
||||
|
||||
@property (nonatomic, strong) EXUpdatesConfig *config;
|
||||
@property (nonatomic, strong) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, strong) NSURL *directory;
|
||||
@property (nonatomic, strong) id<EXUpdatesSelectionPolicy> selectionPolicy;
|
||||
@property (nonatomic, strong) dispatch_queue_t delegateQueue;
|
||||
|
||||
@property (nonatomic, strong) id<EXUpdatesAppLauncher> candidateLauncher;
|
||||
@property (nonatomic, strong) id<EXUpdatesAppLauncher> finalizedLauncher;
|
||||
@property (nonatomic, strong) EXUpdatesEmbeddedAppLoader *embeddedAppLoader;
|
||||
@property (nonatomic, strong) EXUpdatesRemoteAppLoader *remoteAppLoader;
|
||||
|
||||
@property (nonatomic, strong) NSTimer *timer;
|
||||
@property (nonatomic, assign) BOOL isReadyToLaunch;
|
||||
@property (nonatomic, assign) BOOL isTimerFinished;
|
||||
@property (nonatomic, assign) BOOL hasLaunched;
|
||||
@property (nonatomic, assign) BOOL isUpToDate;
|
||||
@property (nonatomic, strong) dispatch_queue_t loaderTaskQueue;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesAppLoaderTask
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
delegateQueue:(dispatch_queue_t)delegateQueue
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_config = config;
|
||||
_database = database;
|
||||
_directory = directory;
|
||||
_selectionPolicy = selectionPolicy;
|
||||
_isUpToDate = NO;
|
||||
_delegateQueue = delegateQueue;
|
||||
_loaderTaskQueue = dispatch_queue_create("expo.loader.LoaderTaskQueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
if (!_config.isEnabled) {
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
[self->_delegate appLoaderTask:self
|
||||
didFinishWithError:[NSError errorWithDomain:EXUpdatesAppLoaderTaskErrorDomain code:1030 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"EXUpdatesAppLoaderTask was passed a configuration object with updates disabled. You should load updates from an embedded source rather than calling EXUpdatesAppLoaderTask, or enable updates in the configuration."
|
||||
}]];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_config.updateUrl) {
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
[self->_delegate appLoaderTask:self
|
||||
didFinishWithError:[NSError errorWithDomain:EXUpdatesAppLoaderTaskErrorDomain code:1030 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"EXUpdatesAppLoaderTask was passed a configuration object with a null URL. You must pass a nonnull URL in order to use EXUpdatesAppLoaderTask to load updates."
|
||||
}]];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_directory) {
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
[self->_delegate appLoaderTask:self
|
||||
didFinishWithError:[NSError errorWithDomain:EXUpdatesAppLoaderTaskErrorDomain code:1030 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"EXUpdatesAppLoaderTask directory must be nonnull."
|
||||
}]];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
__block BOOL shouldCheckForUpdate = [EXUpdatesUtils shouldCheckForUpdateWithConfig:_config];
|
||||
NSNumber *launchWaitMs = _config.launchWaitMs;
|
||||
if ([launchWaitMs isEqualToNumber:@(0)] || !shouldCheckForUpdate) {
|
||||
self->_isTimerFinished = YES;
|
||||
} else {
|
||||
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:[launchWaitMs doubleValue] / 1000];
|
||||
self->_timer = [[NSTimer alloc] initWithFireDate:fireDate interval:0 target:self selector:@selector(_timerDidFire) userInfo:nil repeats:NO];
|
||||
[[NSRunLoop mainRunLoop] addTimer:self->_timer forMode:NSDefaultRunLoopMode];
|
||||
}
|
||||
|
||||
[self _loadEmbeddedUpdateWithCompletion:^{
|
||||
[self _launchWithCompletion:^(NSError * _Nullable error, BOOL success) {
|
||||
if (!success) {
|
||||
if (!shouldCheckForUpdate){
|
||||
[self _finishWithError:error];
|
||||
}
|
||||
NSLog(@"Failed to launch embedded or launchable update: %@", error.localizedDescription);
|
||||
} else {
|
||||
if (self->_delegate &&
|
||||
![self->_delegate appLoaderTask:self didLoadCachedUpdate:self->_candidateLauncher.launchedUpdate]) {
|
||||
// ignore timer and other settings and force launch a remote update.
|
||||
self->_candidateLauncher = nil;
|
||||
[self _stopTimer];
|
||||
shouldCheckForUpdate = YES;
|
||||
} else {
|
||||
self->_isReadyToLaunch = YES;
|
||||
[self _maybeFinish];
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCheckForUpdate) {
|
||||
[self _loadRemoteUpdateWithCompletion:^(NSError * _Nullable error, EXUpdatesUpdate * _Nullable update) {
|
||||
[self _handleRemoteUpdateLoaded:update error:error];
|
||||
}];
|
||||
} else {
|
||||
[self _runReaper];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_finishWithError:(nullable NSError *)error
|
||||
{
|
||||
dispatch_assert_queue(_loaderTaskQueue);
|
||||
|
||||
if (_hasLaunched) {
|
||||
// we've already fired once, don't do it again
|
||||
return;
|
||||
}
|
||||
_hasLaunched = YES;
|
||||
_finalizedLauncher = _candidateLauncher;
|
||||
|
||||
if (_delegate) {
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
if (self->_isReadyToLaunch && (self->_finalizedLauncher.launchAssetUrl || self->_finalizedLauncher.launchedUpdate.status == EXUpdatesUpdateStatusDevelopment)) {
|
||||
[self->_delegate appLoaderTask:self didFinishWithLauncher:self->_finalizedLauncher isUpToDate:self->_isUpToDate];
|
||||
} else {
|
||||
[self->_delegate appLoaderTask:self didFinishWithError:error ?: [NSError errorWithDomain:EXUpdatesAppLoaderTaskErrorDomain code:1031 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"EXUpdatesAppLoaderTask encountered an unexpected error and could not launch an update."
|
||||
}]];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[self _stopTimer];
|
||||
}
|
||||
|
||||
- (void)_maybeFinish
|
||||
{
|
||||
if (!_isTimerFinished || !_isReadyToLaunch) {
|
||||
// too early, bail out
|
||||
return;
|
||||
}
|
||||
[self _finishWithError:nil];
|
||||
}
|
||||
|
||||
- (void)_timerDidFire
|
||||
{
|
||||
dispatch_async(_loaderTaskQueue, ^{
|
||||
self->_isTimerFinished = YES;
|
||||
[self _maybeFinish];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_stopTimer
|
||||
{
|
||||
if (_timer) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
_isTimerFinished = YES;
|
||||
}
|
||||
|
||||
- (void)_runReaper
|
||||
{
|
||||
if (_finalizedLauncher.launchedUpdate) {
|
||||
[EXUpdatesReaper reapUnusedUpdatesWithConfig:_config
|
||||
database:_database
|
||||
directory:_directory
|
||||
selectionPolicy:_selectionPolicy
|
||||
launchedUpdate:_finalizedLauncher.launchedUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_loadEmbeddedUpdateWithCompletion:(void (^)(void))completion
|
||||
{
|
||||
[EXUpdatesAppLauncherWithDatabase launchableUpdateWithConfig:_config database:_database selectionPolicy:_selectionPolicy completion:^(NSError * _Nullable error, EXUpdatesUpdate * _Nullable launchableUpdate) {
|
||||
if (self->_config.hasEmbeddedUpdate &&
|
||||
[self->_selectionPolicy shouldLoadNewUpdate:[EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:self->_config database:self->_database]
|
||||
withLaunchedUpdate:launchableUpdate]) {
|
||||
self->_embeddedAppLoader = [[EXUpdatesEmbeddedAppLoader alloc] initWithConfig:self->_config database:self->_database directory:self->_directory completionQueue:self->_loaderTaskQueue];
|
||||
[self->_embeddedAppLoader loadUpdateFromEmbeddedManifestWithCallback:^BOOL(EXUpdatesUpdate * _Nonnull update) {
|
||||
// we already checked using selection policy, so we don't need to check again
|
||||
return YES;
|
||||
} success:^(EXUpdatesUpdate * _Nullable update) {
|
||||
completion();
|
||||
} error:^(NSError * _Nonnull error) {
|
||||
completion();
|
||||
}];
|
||||
} else {
|
||||
completion();
|
||||
}
|
||||
} completionQueue:_loaderTaskQueue];
|
||||
}
|
||||
|
||||
- (void)_launchWithCompletion:(void (^)(NSError * _Nullable error, BOOL success))completion
|
||||
{
|
||||
EXUpdatesAppLauncherWithDatabase *launcher = [[EXUpdatesAppLauncherWithDatabase alloc] initWithConfig:_config database:_database directory:_directory completionQueue:_loaderTaskQueue];
|
||||
_candidateLauncher = launcher;
|
||||
[launcher launchUpdateWithSelectionPolicy:_selectionPolicy completion:completion];
|
||||
}
|
||||
|
||||
- (void)_loadRemoteUpdateWithCompletion:(void (^)(NSError * _Nullable error, EXUpdatesUpdate * _Nullable update))completion
|
||||
{
|
||||
_remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_config database:_database directory:_directory completionQueue:_loaderTaskQueue];
|
||||
[_remoteAppLoader loadUpdateFromUrl:_config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) {
|
||||
if ([self->_selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:self->_candidateLauncher.launchedUpdate]) {
|
||||
self->_isUpToDate = NO;
|
||||
if (self->_delegate) {
|
||||
dispatch_async(self->_delegateQueue, ^{
|
||||
[self->_delegate appLoaderTask:self didStartLoadingUpdate:update];
|
||||
});
|
||||
}
|
||||
return YES;
|
||||
} else {
|
||||
self->_isUpToDate = YES;
|
||||
return NO;
|
||||
}
|
||||
} success:^(EXUpdatesUpdate * _Nullable update) {
|
||||
completion(nil, update);
|
||||
} error:^(NSError *error) {
|
||||
completion(error, nil);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_handleRemoteUpdateLoaded:(nullable EXUpdatesUpdate *)update error:(nullable NSError *)error
|
||||
{
|
||||
// If the app has not yet been launched (because the timer is still running),
|
||||
// create a new launcher so that we can launch with the newly downloaded update.
|
||||
// Otherwise, we've already launched. Send an event to the notify JS of the new update.
|
||||
|
||||
dispatch_async(_loaderTaskQueue, ^{
|
||||
[self _stopTimer];
|
||||
|
||||
if (update) {
|
||||
if (!self->_hasLaunched) {
|
||||
EXUpdatesAppLauncherWithDatabase *newLauncher = [[EXUpdatesAppLauncherWithDatabase alloc] initWithConfig:self->_config database:self->_database directory:self->_directory completionQueue:self->_loaderTaskQueue];
|
||||
[newLauncher launchUpdateWithSelectionPolicy:self->_selectionPolicy completion:^(NSError * _Nullable error, BOOL success) {
|
||||
if (success) {
|
||||
if (!self->_hasLaunched) {
|
||||
self->_candidateLauncher = newLauncher;
|
||||
self->_isReadyToLaunch = YES;
|
||||
self->_isUpToDate = YES;
|
||||
[self _finishWithError:nil];
|
||||
}
|
||||
} else {
|
||||
[self _finishWithError:error];
|
||||
NSLog(@"Downloaded update but failed to relaunch: %@", error.localizedDescription);
|
||||
}
|
||||
[self _runReaper];
|
||||
}];
|
||||
} else {
|
||||
[self _didFinishBackgroundUpdateWithStatus:EXUpdatesBackgroundUpdateStatusUpdateAvailable manifest:update error:nil];
|
||||
[self _runReaper];
|
||||
}
|
||||
} else {
|
||||
// there's no update, so signal we're ready to launch
|
||||
[self _finishWithError:error];
|
||||
if (error) {
|
||||
[self _didFinishBackgroundUpdateWithStatus:EXUpdatesBackgroundUpdateStatusError manifest:nil error:error];
|
||||
} else {
|
||||
[self _didFinishBackgroundUpdateWithStatus:EXUpdatesBackgroundUpdateStatusNoUpdateAvailable manifest:nil error:nil];
|
||||
}
|
||||
[self _runReaper];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_didFinishBackgroundUpdateWithStatus:(EXUpdatesBackgroundUpdateStatus)status manifest:(nullable EXUpdatesUpdate *)manifest error:(nullable NSError *)error
|
||||
{
|
||||
if (_delegate) {
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
[self->_delegate appLoaderTask:self didFinishBackgroundUpdateWithStatus:status update:manifest error:error];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
32
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAsset.h
generated
vendored
Normal file
32
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAsset.h
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesAsset : NSObject
|
||||
|
||||
/**
|
||||
* properties determined by asset source
|
||||
*/
|
||||
@property (nonatomic, strong) NSString *key;
|
||||
@property (nonatomic, strong) NSString *type;
|
||||
@property (nullable, nonatomic, strong) NSURL *url;
|
||||
@property (nullable, nonatomic, strong) NSDictionary *metadata;
|
||||
@property (nullable, nonatomic, strong) NSString *mainBundleDir; // used for embedded assets
|
||||
@property (nullable, nonatomic, strong) NSString *mainBundleFilename; // used for embedded assets
|
||||
@property (nonatomic, assign) BOOL isLaunchAsset;
|
||||
|
||||
/**
|
||||
* properties determined at runtime by updates implementation
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) NSDate *downloadTime;
|
||||
@property (nullable, nonatomic, strong) NSString *filename;
|
||||
@property (nullable, nonatomic, strong) NSString *contentHash;
|
||||
@property (nullable, nonatomic, strong) NSDictionary *headers;
|
||||
|
||||
- (instancetype)initWithKey:(NSString *)key type:(NSString *)type;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
26
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAsset.m
generated
vendored
Normal file
26
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesAsset.m
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation EXUpdatesAsset
|
||||
|
||||
- (instancetype)initWithKey:(NSString *)key type:(NSString *)type
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_key = key;
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable NSString *)filename
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
21
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesCrypto.h
generated
vendored
Normal file
21
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesCrypto.h
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^EXUpdatesVerifySignatureSuccessBlock)(BOOL isValid);
|
||||
typedef void (^EXUpdatesVerifySignatureErrorBlock)(NSError *error);
|
||||
|
||||
@interface EXUpdatesCrypto : NSObject
|
||||
|
||||
+ (void)verifySignatureWithData:(NSString *)data
|
||||
signature:(NSString *)signature
|
||||
config:(EXUpdatesConfig *)config
|
||||
cacheDirectory:(NSURL *)cacheDirectory
|
||||
successBlock:(EXUpdatesVerifySignatureSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesVerifySignatureErrorBlock)errorBlock;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
217
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesCrypto.m
generated
vendored
Normal file
217
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesCrypto.m
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
// Copyright 2019-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <EXUpdates/EXUpdatesCrypto.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesCryptoPublicKeyUrl = @"https://exp.host/--/manifest-public-key";
|
||||
static NSString * const EXUpdatesCryptoPublicKeyTag = @"exp.host.publickey";
|
||||
static NSString * const EXUpdatesCryptoPublicKeyFilename = @"manifestPublicKey.pem";
|
||||
|
||||
@implementation EXUpdatesCrypto
|
||||
|
||||
+ (void)verifySignatureWithData:(NSString *)data
|
||||
signature:(NSString *)signature
|
||||
config:(EXUpdatesConfig *)config
|
||||
cacheDirectory:(NSURL *)cacheDirectory
|
||||
successBlock:(EXUpdatesVerifySignatureSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesVerifySignatureErrorBlock)errorBlock
|
||||
{
|
||||
[self fetchAndVerifySignatureWithData:data
|
||||
signature:signature
|
||||
config:config
|
||||
cacheDirectory:cacheDirectory
|
||||
useCache:YES
|
||||
successBlock:successBlock
|
||||
errorBlock:errorBlock];
|
||||
}
|
||||
|
||||
+ (void)fetchAndVerifySignatureWithData:(NSString *)data
|
||||
signature:(NSString *)signature
|
||||
config:(EXUpdatesConfig *)config
|
||||
cacheDirectory:(NSURL *)cacheDirectory
|
||||
useCache:(BOOL)useCache
|
||||
successBlock:(EXUpdatesVerifySignatureSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesVerifySignatureErrorBlock)errorBlock
|
||||
{
|
||||
if (!data || !signature) {
|
||||
errorBlock([NSError errorWithDomain:@"EXUpdatesCrypto" code:1001 userInfo:@{ NSLocalizedDescriptionKey: @"Cannot verify the manifest because it is empty or has no signature." }]);
|
||||
return;
|
||||
}
|
||||
|
||||
NSURL *cachedPublicKeyUrl = [cacheDirectory URLByAppendingPathComponent:EXUpdatesCryptoPublicKeyFilename];
|
||||
if (useCache) {
|
||||
NSData *publicKeyData = [NSData dataWithContentsOfFile:[cachedPublicKeyUrl absoluteString]];
|
||||
[[self class] verifyWithPublicKey:publicKeyData signature:signature signedString:data callback:^(BOOL isValid) {
|
||||
if (isValid) {
|
||||
successBlock(isValid);
|
||||
} else {
|
||||
[[self class] fetchAndVerifySignatureWithData:data
|
||||
signature:signature
|
||||
config:config
|
||||
cacheDirectory:cacheDirectory
|
||||
useCache:NO
|
||||
successBlock:successBlock
|
||||
errorBlock:errorBlock];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
NSURLSessionConfiguration *configuration = NSURLSessionConfiguration.defaultSessionConfiguration;
|
||||
configuration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
|
||||
EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:config URLSessionConfiguration:configuration];
|
||||
[fileDownloader downloadFileFromURL:[NSURL URLWithString:EXUpdatesCryptoPublicKeyUrl]
|
||||
toPath:[cachedPublicKeyUrl path]
|
||||
successBlock:^(NSData *publicKeyData, NSURLResponse *response) {
|
||||
[[self class] verifyWithPublicKey:publicKeyData signature:signature signedString:data callback:successBlock];
|
||||
}
|
||||
errorBlock:^(NSError *error, NSURLResponse *response) {
|
||||
errorBlock(error);
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)verifyWithPublicKey:(NSData *)publicKeyData
|
||||
signature:(NSString *)signature
|
||||
signedString:(NSString *)signedString
|
||||
callback:(EXUpdatesVerifySignatureSuccessBlock)callback
|
||||
{
|
||||
if (!publicKeyData) {
|
||||
callback(NO);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SecKeyRef publicKey = [self keyRefFromPEMData:publicKeyData];
|
||||
|
||||
NSData *signatureData = [[NSData alloc] initWithBase64EncodedString:signature options:0];
|
||||
NSData *signedData = [signedString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
BOOL isValid = NO;
|
||||
if (publicKey) {
|
||||
isValid = [self verifyRSASHA256SignedData:signedData signatureData:signatureData publicKey:publicKey];
|
||||
CFRelease(publicKey);
|
||||
}
|
||||
callback(isValid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CFRef to a SecKey given the raw pem data.
|
||||
* The CFRef should be CFReleased when you're finished.
|
||||
*
|
||||
* Here is the Apple doc for this black hole:
|
||||
* https://developer.apple.com/library/prerelease/content/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW13
|
||||
*/
|
||||
+ (nullable SecKeyRef)keyRefFromPEMData:(NSData *)pemData
|
||||
{
|
||||
NSString *pemString = [[NSString alloc] initWithData:pemData encoding:NSUTF8StringEncoding];
|
||||
|
||||
NSString *key = [NSString string];
|
||||
NSArray<NSString *> *keyLines = [pemString componentsSeparatedByString:@"\n"];
|
||||
BOOL foundKey = NO;
|
||||
|
||||
for (NSString *line in keyLines) {
|
||||
if ([line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) {
|
||||
foundKey = YES;
|
||||
} else if ([line isEqualToString:@"-----END PUBLIC KEY-----"]) {
|
||||
foundKey = NO;
|
||||
} else if (foundKey) {
|
||||
key = [key stringByAppendingString:line];
|
||||
}
|
||||
}
|
||||
|
||||
if (key.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *keyData = [[NSData alloc] initWithBase64EncodedString:key options:0];
|
||||
if (keyData == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *tag = [NSData dataWithBytes:[EXUpdatesCryptoPublicKeyTag UTF8String] length:[EXUpdatesCryptoPublicKeyTag length]];
|
||||
|
||||
// Delete any old lingering key with the same tag.
|
||||
NSDictionary *deleteParams = @{
|
||||
(__bridge id)kSecClass: (__bridge id)kSecClassKey,
|
||||
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
|
||||
(__bridge id)kSecAttrApplicationTag: tag,
|
||||
};
|
||||
OSStatus secStatus = SecItemDelete((CFDictionaryRef)deleteParams);
|
||||
|
||||
SecKeyRef savedKeyRef = nil;
|
||||
|
||||
// Add key to system keychain.
|
||||
NSDictionary *saveParams = @{
|
||||
(__bridge id)kSecClass: (__bridge id) kSecClassKey,
|
||||
(__bridge id)kSecAttrKeyType: (__bridge id) kSecAttrKeyTypeRSA,
|
||||
(__bridge id)kSecAttrApplicationTag: tag,
|
||||
(__bridge id)kSecAttrKeyClass: (__bridge id) kSecAttrKeyClassPublic,
|
||||
(__bridge id)kSecReturnPersistentRef: (__bridge id)kCFBooleanTrue,
|
||||
(__bridge id)kSecValueData: keyData,
|
||||
(__bridge id)kSecAttrKeySizeInBits: [NSNumber numberWithUnsignedInteger:keyData.length],
|
||||
(__bridge id)kSecAttrEffectiveKeySize: [NSNumber numberWithUnsignedInteger:keyData.length],
|
||||
(__bridge id)kSecAttrCanDerive: (__bridge id) kCFBooleanFalse,
|
||||
(__bridge id)kSecAttrCanEncrypt: (__bridge id) kCFBooleanTrue,
|
||||
(__bridge id)kSecAttrCanDecrypt: (__bridge id) kCFBooleanFalse,
|
||||
(__bridge id)kSecAttrCanVerify: (__bridge id) kCFBooleanTrue,
|
||||
(__bridge id)kSecAttrCanSign: (__bridge id) kCFBooleanFalse,
|
||||
(__bridge id)kSecAttrCanWrap: (__bridge id) kCFBooleanTrue,
|
||||
(__bridge id)kSecAttrCanUnwrap: (__bridge id) kCFBooleanFalse,
|
||||
};
|
||||
|
||||
secStatus = SecItemAdd((CFDictionaryRef)saveParams, (CFTypeRef *)&savedKeyRef);
|
||||
|
||||
if (savedKeyRef != nil) {
|
||||
CFRelease(savedKeyRef);
|
||||
}
|
||||
|
||||
if (secStatus != noErr && secStatus != errSecDuplicateItem) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Fetch the SecKeyRef version of the key.
|
||||
// note that kSecAttrKeyClass: kSecAttrKeyClassPublic doesn't seem to be required here.
|
||||
// also: this doesn't work on iOS < 10.0
|
||||
SecKeyRef keyRef = nil;
|
||||
NSDictionary *queryParams = @{
|
||||
(__bridge id)kSecClass: (__bridge id) kSecClassKey,
|
||||
(__bridge id)kSecAttrKeyType: (__bridge id) kSecAttrKeyTypeRSA,
|
||||
(__bridge id)kSecAttrApplicationTag: tag,
|
||||
(__bridge id)kSecReturnRef: (__bridge id) kCFBooleanTrue,
|
||||
};
|
||||
secStatus = SecItemCopyMatching((CFDictionaryRef)queryParams, (CFTypeRef *)&keyRef);
|
||||
|
||||
if (secStatus != noErr) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return keyRef;
|
||||
}
|
||||
|
||||
+ (BOOL)verifyRSASHA256SignedData:(NSData *)signedData signatureData:(NSData *)signatureData publicKey:(nullable SecKeyRef)publicKey
|
||||
{
|
||||
if (!publicKey) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
uint8_t hashBytes[CC_SHA256_DIGEST_LENGTH];
|
||||
if (!CC_SHA256([signedData bytes], (CC_LONG)[signedData length], hashBytes)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
OSStatus status = SecKeyRawVerify(publicKey,
|
||||
kSecPaddingPKCS1SHA256,
|
||||
hashBytes,
|
||||
CC_SHA256_DIGEST_LENGTH,
|
||||
[signatureData bytes],
|
||||
[signatureData length]);
|
||||
|
||||
return status == errSecSuccess;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
25
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesEmbeddedAppLoader.h
generated
vendored
Normal file
25
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesEmbeddedAppLoader.h
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLoader+Private.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString * const EXUpdatesEmbeddedManifestName;
|
||||
extern NSString * const EXUpdatesEmbeddedManifestType;
|
||||
extern NSString * const EXUpdatesEmbeddedBundleFilename;
|
||||
extern NSString * const EXUpdatesEmbeddedBundleFileType;
|
||||
extern NSString * const EXUpdatesBareEmbeddedBundleFilename;
|
||||
extern NSString * const EXUpdatesBareEmbeddedBundleFileType;
|
||||
|
||||
@interface EXUpdatesEmbeddedAppLoader : EXUpdatesAppLoader
|
||||
|
||||
+ (nullable EXUpdatesUpdate *)embeddedManifestWithConfig:(EXUpdatesConfig *)config
|
||||
database:(nullable EXUpdatesDatabase *)database;
|
||||
|
||||
- (void)loadUpdateFromEmbeddedManifestWithCallback:(EXUpdatesAppLoaderManifestBlock)manifestBlock
|
||||
success:(EXUpdatesAppLoaderSuccessBlock)success
|
||||
error:(EXUpdatesAppLoaderErrorBlock)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
124
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesEmbeddedAppLoader.m
generated
vendored
Normal file
124
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesEmbeddedAppLoader.m
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
|
||||
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
|
37
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.h
generated
vendored
Normal file
37
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.h
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^EXUpdatesFileDownloaderSuccessBlock)(NSData *data, NSURLResponse *response);
|
||||
typedef void (^EXUpdatesFileDownloaderManifestSuccessBlock)(EXUpdatesUpdate *update);
|
||||
typedef void (^EXUpdatesFileDownloaderErrorBlock)(NSError *error, NSURLResponse *response);
|
||||
|
||||
@interface EXUpdatesFileDownloader : NSObject
|
||||
|
||||
- (instancetype)initWithUpdatesConfig:(EXUpdatesConfig *)updatesConfig;
|
||||
- (instancetype)initWithUpdatesConfig:(EXUpdatesConfig *)updatesConfig
|
||||
URLSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration;
|
||||
|
||||
- (void)downloadDataFromURL:(NSURL *)url
|
||||
successBlock:(EXUpdatesFileDownloaderSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock;
|
||||
|
||||
- (void)downloadFileFromURL:(NSURL *)url
|
||||
toPath:(NSString *)destinationPath
|
||||
successBlock:(EXUpdatesFileDownloaderSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock;
|
||||
|
||||
- (void)downloadManifestFromURL:(NSURL *)url
|
||||
withDatabase:(EXUpdatesDatabase *)database
|
||||
cacheDirectory:(NSURL *)cacheDirectory
|
||||
successBlock:(EXUpdatesFileDownloaderManifestSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock;
|
||||
|
||||
+ (dispatch_queue_t)assetFilesQueue;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
291
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.m
generated
vendored
Normal file
291
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.m
generated
vendored
Normal file
@ -0,0 +1,291 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLauncherNoDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesCrypto.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString * const EXUpdatesFileDownloaderErrorDomain = @"EXUpdatesFileDownloader";
|
||||
NSTimeInterval const EXUpdatesDefaultTimeoutInterval = 60;
|
||||
|
||||
@interface EXUpdatesFileDownloader () <NSURLSessionDataDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSURLSession *session;
|
||||
@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
|
||||
@property (nonatomic, strong) EXUpdatesConfig *config;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesFileDownloader
|
||||
|
||||
- (instancetype)initWithUpdatesConfig:(EXUpdatesConfig *)updatesConfig
|
||||
{
|
||||
return [self initWithUpdatesConfig:updatesConfig
|
||||
URLSessionConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration];
|
||||
}
|
||||
|
||||
- (instancetype)initWithUpdatesConfig:(EXUpdatesConfig *)updatesConfig
|
||||
URLSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_sessionConfiguration = sessionConfiguration;
|
||||
_session = [NSURLSession sessionWithConfiguration:_sessionConfiguration delegate:self delegateQueue:nil];
|
||||
_config = updatesConfig;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_session finishTasksAndInvalidate];
|
||||
}
|
||||
|
||||
+ (dispatch_queue_t)assetFilesQueue
|
||||
{
|
||||
static dispatch_queue_t theQueue;
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
if (!theQueue) {
|
||||
theQueue = dispatch_queue_create("expo.controller.AssetFilesQueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
});
|
||||
return theQueue;
|
||||
}
|
||||
|
||||
- (void)downloadFileFromURL:(NSURL *)url
|
||||
toPath:(NSString *)destinationPath
|
||||
successBlock:(EXUpdatesFileDownloaderSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock
|
||||
{
|
||||
[self downloadDataFromURL:url successBlock:^(NSData *data, NSURLResponse *response) {
|
||||
NSError *error;
|
||||
if ([data writeToFile:destinationPath options:NSDataWritingAtomic error:&error]) {
|
||||
successBlock(data, response);
|
||||
} else {
|
||||
errorBlock([NSError errorWithDomain:EXUpdatesFileDownloaderErrorDomain
|
||||
code:1002
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Could not write to path %@: %@", destinationPath, error.localizedDescription],
|
||||
NSUnderlyingErrorKey: error
|
||||
}
|
||||
], response);
|
||||
}
|
||||
} errorBlock:errorBlock];
|
||||
}
|
||||
|
||||
- (void)downloadManifestFromURL:(NSURL *)url
|
||||
withDatabase:(EXUpdatesDatabase *)database
|
||||
cacheDirectory:(NSURL *)cacheDirectory
|
||||
successBlock:(EXUpdatesFileDownloaderManifestSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock
|
||||
{
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
|
||||
cachePolicy:NSURLRequestReloadIgnoringCacheData
|
||||
timeoutInterval:EXUpdatesDefaultTimeoutInterval];
|
||||
[self _setManifestHTTPHeaderFields:request];
|
||||
[self _downloadDataWithRequest:request successBlock:^(NSData *data, NSURLResponse *response) {
|
||||
NSError *err;
|
||||
id parsedJson = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&err];
|
||||
if (err) {
|
||||
errorBlock(err, response);
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *manifest = [self _extractManifest:parsedJson error:&err];
|
||||
if (err) {
|
||||
errorBlock(err, response);
|
||||
return;
|
||||
}
|
||||
|
||||
id innerManifestString = manifest[@"manifestString"];
|
||||
id signature = manifest[@"signature"];
|
||||
BOOL isSigned = innerManifestString != nil && signature != nil;
|
||||
|
||||
// XDL serves unsigned manifests with the `signature` key set to "UNSIGNED".
|
||||
// We should treat these manifests as unsigned rather than signed with an invalid signature.
|
||||
if (isSigned && [signature isKindOfClass:[NSString class]] && [(NSString *)signature isEqualToString:@"UNSIGNED"]) {
|
||||
isSigned = NO;
|
||||
|
||||
NSError *err;
|
||||
manifest = [NSJSONSerialization JSONObjectWithData:[(NSString *)innerManifestString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&err];
|
||||
NSAssert(!err && manifest && [manifest isKindOfClass:[NSDictionary class]], @"manifest should be a valid JSON object");
|
||||
NSMutableDictionary *mutableManifest = [manifest mutableCopy];
|
||||
mutableManifest[@"isVerified"] = @(NO);
|
||||
manifest = [mutableManifest copy];
|
||||
}
|
||||
|
||||
if (isSigned) {
|
||||
NSAssert([innerManifestString isKindOfClass:[NSString class]], @"manifestString should be a string");
|
||||
NSAssert([signature isKindOfClass:[NSString class]], @"signature should be a string");
|
||||
[EXUpdatesCrypto verifySignatureWithData:(NSString *)innerManifestString
|
||||
signature:(NSString *)signature
|
||||
config:self->_config
|
||||
cacheDirectory:cacheDirectory
|
||||
successBlock:^(BOOL isValid) {
|
||||
if (isValid) {
|
||||
NSError *err;
|
||||
id innerManifest = [NSJSONSerialization JSONObjectWithData:[(NSString *)innerManifestString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&err];
|
||||
NSAssert(!err && innerManifest && [innerManifest isKindOfClass:[NSDictionary class]], @"manifest should be a valid JSON object");
|
||||
NSMutableDictionary *mutableInnerManifest = [(NSDictionary *)innerManifest mutableCopy];
|
||||
mutableInnerManifest[@"isVerified"] = @(YES);
|
||||
EXUpdatesUpdate *update = [EXUpdatesUpdate updateWithManifest:[mutableInnerManifest copy]
|
||||
config:self->_config
|
||||
database:database];
|
||||
successBlock(update);
|
||||
} else {
|
||||
NSError *error = [NSError errorWithDomain:EXUpdatesFileDownloaderErrorDomain code:1003 userInfo:@{NSLocalizedDescriptionKey: @"Manifest verification failed"}];
|
||||
errorBlock(error, response);
|
||||
}
|
||||
}
|
||||
errorBlock:^(NSError *error) {
|
||||
errorBlock(error, response);
|
||||
}
|
||||
];
|
||||
} else {
|
||||
EXUpdatesUpdate *update = [EXUpdatesUpdate updateWithManifest:(NSDictionary *)manifest
|
||||
config:self->_config
|
||||
database:database];
|
||||
successBlock(update);
|
||||
}
|
||||
} errorBlock:errorBlock];
|
||||
}
|
||||
|
||||
- (void)downloadDataFromURL:(NSURL *)url
|
||||
successBlock:(EXUpdatesFileDownloaderSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock
|
||||
{
|
||||
// pass any custom cache policy onto this specific request
|
||||
NSURLRequestCachePolicy cachePolicy = _sessionConfiguration ? _sessionConfiguration.requestCachePolicy : NSURLRequestUseProtocolCachePolicy;
|
||||
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:EXUpdatesDefaultTimeoutInterval];
|
||||
[self _setHTTPHeaderFields:request];
|
||||
|
||||
[self _downloadDataWithRequest:request successBlock:successBlock errorBlock:errorBlock];
|
||||
}
|
||||
|
||||
- (void)_downloadDataWithRequest:(NSURLRequest *)request
|
||||
successBlock:(EXUpdatesFileDownloaderSuccessBlock)successBlock
|
||||
errorBlock:(EXUpdatesFileDownloaderErrorBlock)errorBlock
|
||||
{
|
||||
NSURLSessionDataTask *task = [_session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (httpResponse.statusCode != 200) {
|
||||
NSStringEncoding encoding = [self _encodingFromResponse:response];
|
||||
NSString *body = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
error = [self _errorFromResponse:httpResponse body:body];
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
errorBlock(error, response);
|
||||
} else {
|
||||
successBlock(data, response);
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (nullable NSDictionary *)_extractManifest:(id)parsedJson error:(NSError **)error
|
||||
{
|
||||
if ([parsedJson isKindOfClass:[NSDictionary class]]) {
|
||||
return (NSDictionary *)parsedJson;
|
||||
} else if ([parsedJson isKindOfClass:[NSArray class]]) {
|
||||
// TODO: either add support for runtimeVersion or deprecate multi-manifests
|
||||
for (id providedManifest in (NSArray *)parsedJson) {
|
||||
if ([providedManifest isKindOfClass:[NSDictionary class]] && providedManifest[@"sdkVersion"]){
|
||||
NSString *sdkVersion = providedManifest[@"sdkVersion"];
|
||||
NSArray<NSString *> *supportedSdkVersions = [_config.sdkVersion componentsSeparatedByString:@","];
|
||||
if ([supportedSdkVersions containsObject:sdkVersion]){
|
||||
return providedManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:EXUpdatesFileDownloaderErrorDomain code:1009 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"No compatible update found at %@. Only %@ are supported.", _config.updateUrl.absoluteString, _config.sdkVersion]}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_setHTTPHeaderFields:(NSMutableURLRequest *)request
|
||||
{
|
||||
[request setValue:@"ios" forHTTPHeaderField:@"Expo-Platform"];
|
||||
[request setValue:@"1" forHTTPHeaderField:@"Expo-Api-Version"];
|
||||
[request setValue:@"BARE" forHTTPHeaderField:@"Expo-Updates-Environment"];
|
||||
|
||||
for (NSString *key in _config.requestHeaders) {
|
||||
[request setValue:_config.requestHeaders[key] forHTTPHeaderField:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_setManifestHTTPHeaderFields:(NSMutableURLRequest *)request
|
||||
{
|
||||
[request setValue:@"application/expo+json,application/json" forHTTPHeaderField:@"Accept"];
|
||||
[request setValue:@"true" forHTTPHeaderField:@"Expo-JSON-Error"];
|
||||
[request setValue:@"true" forHTTPHeaderField:@"Expo-Accept-Signature"];
|
||||
[request setValue:_config.releaseChannel forHTTPHeaderField:@"Expo-Release-Channel"];
|
||||
|
||||
NSString *runtimeVersion = _config.runtimeVersion;
|
||||
if (runtimeVersion) {
|
||||
[request setValue:runtimeVersion forHTTPHeaderField:@"Expo-Runtime-Version"];
|
||||
} else {
|
||||
[request setValue:_config.sdkVersion forHTTPHeaderField:@"Expo-SDK-Version"];
|
||||
}
|
||||
|
||||
NSString *previousFatalError = [EXUpdatesAppLauncherNoDatabase consumeError];
|
||||
if (previousFatalError) {
|
||||
// some servers can have max length restrictions for headers,
|
||||
// so we restrict the length of the string to 1024 characters --
|
||||
// this should satisfy the requirements of most servers
|
||||
if ([previousFatalError length] > 1024) {
|
||||
previousFatalError = [previousFatalError substringToIndex:1024];
|
||||
}
|
||||
[request setValue:previousFatalError forHTTPHeaderField:@"Expo-Fatal-Error"];
|
||||
}
|
||||
|
||||
[self _setHTTPHeaderFields:request];
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionTaskDelegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||||
{
|
||||
completionHandler(request);
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDataDelegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
|
||||
{
|
||||
completionHandler(proposedResponse);
|
||||
}
|
||||
|
||||
#pragma mark - Parsing the response
|
||||
|
||||
- (NSStringEncoding)_encodingFromResponse:(NSURLResponse *)response
|
||||
{
|
||||
if (response.textEncodingName) {
|
||||
CFStringRef cfEncodingName = (__bridge CFStringRef)response.textEncodingName;
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding(cfEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
return CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
// Default to UTF-8
|
||||
return NSUTF8StringEncoding;
|
||||
}
|
||||
|
||||
- (NSError *)_errorFromResponse:(NSHTTPURLResponse *)response body:(NSString *)body
|
||||
{
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: body,
|
||||
};
|
||||
return [NSError errorWithDomain:EXUpdatesFileDownloaderErrorDomain code:response.statusCode userInfo:userInfo];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
11
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.h
generated
vendored
Normal file
11
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.h
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLoader+Private.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesRemoteAppLoader : EXUpdatesAppLoader
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
77
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.m
generated
vendored
Normal file
77
node_modules/expo-updates/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.m
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesRemoteAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesCrypto.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesRemoteAppLoader ()
|
||||
|
||||
@property (nonatomic, strong) EXUpdatesFileDownloader *downloader;
|
||||
|
||||
@end
|
||||
static NSString * const EXUpdatesRemoteAppLoaderErrorDomain = @"EXUpdatesRemoteAppLoader";
|
||||
|
||||
@implementation EXUpdatesRemoteAppLoader
|
||||
|
||||
- (instancetype)initWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
completionQueue:(dispatch_queue_t)completionQueue
|
||||
{
|
||||
if (self = [super initWithConfig:config database:database directory:directory completionQueue:completionQueue]) {
|
||||
_downloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:self.config];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadUpdateFromUrl:(NSURL *)url
|
||||
onManifest:(EXUpdatesAppLoaderManifestBlock)manifestBlock
|
||||
success:(EXUpdatesAppLoaderSuccessBlock)success
|
||||
error:(EXUpdatesAppLoaderErrorBlock)error
|
||||
{
|
||||
self.manifestBlock = manifestBlock;
|
||||
self.successBlock = success;
|
||||
self.errorBlock = error;
|
||||
[_downloader downloadManifestFromURL:url withDatabase:self.database cacheDirectory:self.directory successBlock:^(EXUpdatesUpdate *update) {
|
||||
[self startLoadingFromManifest:update];
|
||||
} errorBlock:^(NSError *error, NSURLResponse *response) {
|
||||
if (self.errorBlock) {
|
||||
self.errorBlock(error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)downloadAsset:(EXUpdatesAsset *)asset
|
||||
{
|
||||
NSURL *urlOnDisk = [self.directory URLByAppendingPathComponent:asset.filename];
|
||||
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:[urlOnDisk path]]) {
|
||||
// file already exists, we don't need to download it again
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self handleAssetDownloadAlreadyExists:asset];
|
||||
});
|
||||
} else {
|
||||
if (!asset.url) {
|
||||
[self handleAssetDownloadWithError:[NSError errorWithDomain:EXUpdatesRemoteAppLoaderErrorDomain code:1006 userInfo:@{NSLocalizedDescriptionKey: @"Failed to download asset with no URL provided"}] asset:asset];
|
||||
return;
|
||||
}
|
||||
|
||||
[self->_downloader downloadFileFromURL:asset.url toPath:[urlOnDisk path] successBlock:^(NSData *data, NSURLResponse *response) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self handleAssetDownloadWithData:data response:response asset:asset];
|
||||
});
|
||||
} errorBlock:^(NSError *error, NSURLResponse *response) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self handleAssetDownloadWithError:error asset:asset];
|
||||
});
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
39
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesDatabase.h
generated
vendored
Normal file
39
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesDatabase.h
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, EXUpdatesDatabaseHashType) {
|
||||
EXUpdatesDatabaseHashTypeSha1 = 0
|
||||
};
|
||||
|
||||
@interface EXUpdatesDatabase : NSObject
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t databaseQueue;
|
||||
|
||||
- (BOOL)openDatabaseInDirectory:(NSURL *)directory withError:(NSError ** _Nullable)error;
|
||||
- (void)closeDatabase;
|
||||
|
||||
- (void)addUpdate:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error;
|
||||
- (void)addNewAssets:(NSArray<EXUpdatesAsset *> *)assets toUpdateWithId:(NSUUID *)updateId error:(NSError ** _Nullable)error;
|
||||
- (BOOL)addExistingAsset:(EXUpdatesAsset *)asset toUpdateWithId:(NSUUID *)updateId error:(NSError ** _Nullable)error;
|
||||
- (void)updateAsset:(EXUpdatesAsset *)asset error:(NSError ** _Nullable)error;
|
||||
- (void)mergeAsset:(EXUpdatesAsset *)asset withExistingEntry:(EXUpdatesAsset *)existingAsset error:(NSError ** _Nullable)error;
|
||||
- (void)markUpdateFinished:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error;
|
||||
- (void)setScopeKey:(NSString *)scopeKey onUpdate:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error;
|
||||
|
||||
- (void)deleteUpdates:(NSArray<EXUpdatesUpdate *> *)updates error:(NSError ** _Nullable)error;
|
||||
- (nullable NSArray<EXUpdatesAsset *> *)deleteUnusedAssetsWithError:(NSError ** _Nullable)error;
|
||||
|
||||
- (nullable NSArray<EXUpdatesUpdate *> *)allUpdatesWithConfig:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error;
|
||||
- (nullable NSArray<EXUpdatesUpdate *> *)launchableUpdatesWithConfig:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error;
|
||||
- (nullable EXUpdatesUpdate *)updateWithId:(NSUUID *)updateId config:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error;
|
||||
- (nullable NSArray<EXUpdatesAsset *> *)assetsWithUpdateId:(NSUUID *)updateId error:(NSError ** _Nullable)error;
|
||||
- (nullable EXUpdatesAsset *)assetWithKey:(NSString *)key error:(NSError ** _Nullable)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
644
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesDatabase.m
generated
vendored
Normal file
644
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesDatabase.m
generated
vendored
Normal file
@ -0,0 +1,644 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
|
||||
#import <sqlite3.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesDatabase ()
|
||||
|
||||
@property (nonatomic, assign) sqlite3 *db;
|
||||
@property (nonatomic, readwrite, strong) NSLock *lock;
|
||||
|
||||
@end
|
||||
|
||||
static NSString * const EXUpdatesDatabaseErrorDomain = @"EXUpdatesDatabase";
|
||||
static NSString * const EXUpdatesDatabaseFilename = @"expo-v4.db";
|
||||
|
||||
@implementation EXUpdatesDatabase
|
||||
|
||||
# pragma mark - lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_databaseQueue = dispatch_queue_create("expo.database.DatabaseQueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)openDatabaseInDirectory:(NSURL *)directory withError:(NSError ** _Nullable)error
|
||||
{
|
||||
sqlite3 *db;
|
||||
NSURL *dbUrl = [directory URLByAppendingPathComponent:EXUpdatesDatabaseFilename];
|
||||
BOOL shouldInitializeDatabase = ![[NSFileManager defaultManager] fileExistsAtPath:[dbUrl path]];
|
||||
int resultCode = sqlite3_open([[dbUrl path] UTF8String], &db);
|
||||
if (resultCode != SQLITE_OK) {
|
||||
NSLog(@"Error opening SQLite db: %@", [self _errorFromSqlite:_db].localizedDescription);
|
||||
sqlite3_close(db);
|
||||
|
||||
if (resultCode == SQLITE_CORRUPT || resultCode == SQLITE_NOTADB) {
|
||||
NSString *archivedDbFilename = [NSString stringWithFormat:@"%f-%@", [[NSDate date] timeIntervalSince1970], EXUpdatesDatabaseFilename];
|
||||
NSURL *destinationUrl = [directory URLByAppendingPathComponent:archivedDbFilename];
|
||||
NSError *err;
|
||||
if ([[NSFileManager defaultManager] moveItemAtURL:dbUrl toURL:destinationUrl error:&err]) {
|
||||
NSLog(@"Moved corrupt SQLite db to %@", archivedDbFilename);
|
||||
if (sqlite3_open([[dbUrl absoluteString] UTF8String], &db) != SQLITE_OK) {
|
||||
if (error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
shouldInitializeDatabase = YES;
|
||||
} else {
|
||||
NSString *description = [NSString stringWithFormat:@"Could not move existing corrupt database: %@", [err localizedDescription]];
|
||||
if (error != nil) {
|
||||
*error = [NSError errorWithDomain:EXUpdatesDatabaseErrorDomain
|
||||
code:1004
|
||||
userInfo:@{ NSLocalizedDescriptionKey: description, NSUnderlyingErrorKey: err }];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
_db = db;
|
||||
|
||||
if (shouldInitializeDatabase) {
|
||||
return [self _initializeDatabase:error];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)closeDatabase
|
||||
{
|
||||
sqlite3_close(_db);
|
||||
_db = nil;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self closeDatabase];
|
||||
}
|
||||
|
||||
- (BOOL)_initializeDatabase:(NSError **)error
|
||||
{
|
||||
NSAssert(_db, @"Missing database handle");
|
||||
dispatch_assert_queue(_databaseQueue);
|
||||
|
||||
NSString * const createTableStmts = @"\
|
||||
PRAGMA foreign_keys = ON;\
|
||||
CREATE TABLE \"updates\" (\
|
||||
\"id\" BLOB UNIQUE,\
|
||||
\"scope_key\" TEXT NOT NULL,\
|
||||
\"commit_time\" INTEGER NOT NULL,\
|
||||
\"runtime_version\" TEXT NOT NULL,\
|
||||
\"launch_asset_id\" INTEGER,\
|
||||
\"metadata\" TEXT,\
|
||||
\"status\" INTEGER NOT NULL,\
|
||||
\"keep\" INTEGER NOT NULL,\
|
||||
PRIMARY KEY(\"id\"),\
|
||||
FOREIGN KEY(\"launch_asset_id\") REFERENCES \"assets\"(\"id\") ON DELETE CASCADE\
|
||||
);\
|
||||
CREATE TABLE \"assets\" (\
|
||||
\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,\
|
||||
\"url\" TEXT,\
|
||||
\"key\" TEXT NOT NULL UNIQUE,\
|
||||
\"headers\" TEXT,\
|
||||
\"type\" TEXT NOT NULL,\
|
||||
\"metadata\" TEXT,\
|
||||
\"download_time\" INTEGER NOT NULL,\
|
||||
\"relative_path\" TEXT NOT NULL,\
|
||||
\"hash\" BLOB NOT NULL,\
|
||||
\"hash_type\" INTEGER NOT NULL,\
|
||||
\"marked_for_deletion\" INTEGER NOT NULL\
|
||||
);\
|
||||
CREATE TABLE \"updates_assets\" (\
|
||||
\"update_id\" BLOB NOT NULL,\
|
||||
\"asset_id\" INTEGER NOT NULL,\
|
||||
FOREIGN KEY(\"update_id\") REFERENCES \"updates\"(\"id\") ON DELETE CASCADE,\
|
||||
FOREIGN KEY(\"asset_id\") REFERENCES \"assets\"(\"id\") ON DELETE CASCADE\
|
||||
);\
|
||||
CREATE TABLE \"json_data\" (\
|
||||
\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\
|
||||
\"key\" TEXT NOT NULL,\
|
||||
\"value\" TEXT NOT NULL,\
|
||||
\"last_updated\" INTEGER NOT NULL,\
|
||||
\"scope_key\" TEXT NOT NULL\
|
||||
);\
|
||||
CREATE UNIQUE INDEX \"index_updates_scope_key_commit_time\" ON \"updates\" (\"scope_key\", \"commit_time\");\
|
||||
CREATE INDEX \"index_updates_launch_asset_id\" ON \"updates\" (\"launch_asset_id\");\
|
||||
CREATE INDEX \"index_json_data_scope_key\" ON \"json_data\" (\"scope_key\")\
|
||||
";
|
||||
|
||||
char *errMsg;
|
||||
if (sqlite3_exec(_db, [createTableStmts UTF8String], NULL, NULL, &errMsg) != SQLITE_OK) {
|
||||
if (error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
sqlite3_free(errMsg);
|
||||
return NO;
|
||||
};
|
||||
return YES;
|
||||
}
|
||||
|
||||
# pragma mark - insert and update
|
||||
|
||||
- (void)addUpdate:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const sql = @"INSERT INTO \"updates\" (\"id\", \"scope_key\", \"commit_time\", \"runtime_version\", \"metadata\", \"status\" , \"keep\")\
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, 1);";
|
||||
|
||||
[self _executeSql:sql
|
||||
withArgs:@[
|
||||
update.updateId,
|
||||
update.scopeKey,
|
||||
@([update.commitTime timeIntervalSince1970] * 1000),
|
||||
update.runtimeVersion,
|
||||
update.metadata ?: [NSNull null],
|
||||
@(EXUpdatesUpdateStatusPending)
|
||||
]
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (void)addNewAssets:(NSArray<EXUpdatesAsset *> *)assets toUpdateWithId:(NSUUID *)updateId error:(NSError ** _Nullable)error
|
||||
{
|
||||
sqlite3_exec(_db, "BEGIN;", NULL, NULL, NULL);
|
||||
|
||||
for (EXUpdatesAsset *asset in assets) {
|
||||
NSAssert(asset.downloadTime, @"asset downloadTime should be nonnull");
|
||||
NSAssert(asset.filename, @"asset filename should be nonnull");
|
||||
NSAssert(asset.contentHash, @"asset contentHash should be nonnull");
|
||||
|
||||
NSString * const assetInsertSql = @"INSERT OR REPLACE INTO \"assets\" (\"key\", \"url\", \"headers\", \"type\", \"metadata\", \"download_time\", \"relative_path\", \"hash\", \"hash_type\", \"marked_for_deletion\")\
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 0);";
|
||||
if ([self _executeSql:assetInsertSql
|
||||
withArgs:@[
|
||||
asset.key,
|
||||
asset.url ? asset.url.absoluteString : [NSNull null],
|
||||
asset.headers ?: [NSNull null],
|
||||
asset.type,
|
||||
asset.metadata ?: [NSNull null],
|
||||
@(asset.downloadTime.timeIntervalSince1970 * 1000),
|
||||
asset.filename,
|
||||
asset.contentHash,
|
||||
@(EXUpdatesDatabaseHashTypeSha1)
|
||||
]
|
||||
error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// statements must stay in precisely this order for last_insert_rowid() to work correctly
|
||||
if (asset.isLaunchAsset) {
|
||||
NSString * const updateSql = @"UPDATE updates SET launch_asset_id = last_insert_rowid() WHERE id = ?1;";
|
||||
if ([self _executeSql:updateSql withArgs:@[updateId] error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSString * const updateInsertSql = @"INSERT OR REPLACE INTO updates_assets (\"update_id\", \"asset_id\") VALUES (?1, last_insert_rowid());";
|
||||
if ([self _executeSql:updateInsertSql withArgs:@[updateId] error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_exec(_db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
- (BOOL)addExistingAsset:(EXUpdatesAsset *)asset toUpdateWithId:(NSUUID *)updateId error:(NSError ** _Nullable)error
|
||||
{
|
||||
BOOL success;
|
||||
|
||||
sqlite3_exec(_db, "BEGIN;", NULL, NULL, NULL);
|
||||
|
||||
NSString * const assetSelectSql = @"SELECT id FROM assets WHERE \"key\" = ?1 LIMIT 1;";
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:assetSelectSql withArgs:@[asset.key] error:error];
|
||||
if (!rows || ![rows count]) {
|
||||
success = NO;
|
||||
} else {
|
||||
NSNumber *assetId = rows[0][@"id"];
|
||||
NSString * const insertSql = @"INSERT OR REPLACE INTO updates_assets (\"update_id\", \"asset_id\") VALUES (?1, ?2);";
|
||||
if ([self _executeSql:insertSql withArgs:@[updateId, assetId] error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return NO;
|
||||
}
|
||||
if (asset.isLaunchAsset) {
|
||||
NSString * const updateSql = @"UPDATE updates SET launch_asset_id = ?1 WHERE id = ?2;";
|
||||
if ([self _executeSql:updateSql withArgs:@[assetId, updateId] error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
success = YES;
|
||||
}
|
||||
|
||||
sqlite3_exec(_db, "COMMIT;", NULL, NULL, NULL);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
- (void)updateAsset:(EXUpdatesAsset *)asset error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSAssert(asset.downloadTime, @"asset downloadTime should be nonnull");
|
||||
NSAssert(asset.filename, @"asset filename should be nonnull");
|
||||
NSAssert(asset.contentHash, @"asset contentHash should be nonnull");
|
||||
|
||||
NSString * const assetUpdateSql = @"UPDATE \"assets\" SET \"headers\" = ?2, \"type\" = ?3, \"metadata\" = ?4, \"download_time\" = ?5, \"relative_path\" = ?6, \"hash\" = ?7, \"url\" = ?8 WHERE \"key\" = ?1;";
|
||||
[self _executeSql:assetUpdateSql
|
||||
withArgs:@[
|
||||
asset.key,
|
||||
asset.headers ?: [NSNull null],
|
||||
asset.type,
|
||||
asset.metadata ?: [NSNull null],
|
||||
@(asset.downloadTime.timeIntervalSince1970 * 1000),
|
||||
asset.filename,
|
||||
asset.contentHash,
|
||||
asset.url ? asset.url.absoluteString : [NSNull null]
|
||||
]
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (void)mergeAsset:(EXUpdatesAsset *)asset withExistingEntry:(EXUpdatesAsset *)existingAsset error:(NSError ** _Nullable)error
|
||||
{
|
||||
// if the existing entry came from an embedded manifest, it may not have a URL in the database
|
||||
if (asset.url && !existingAsset.url) {
|
||||
existingAsset.url = asset.url;
|
||||
[self updateAsset:existingAsset error:error];
|
||||
}
|
||||
// all other properties should be overridden by database values
|
||||
asset.filename = existingAsset.filename;
|
||||
asset.contentHash = existingAsset.contentHash;
|
||||
asset.downloadTime = existingAsset.downloadTime;
|
||||
}
|
||||
|
||||
- (void)markUpdateFinished:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error
|
||||
{
|
||||
if (update.status != EXUpdatesUpdateStatusDevelopment) {
|
||||
update.status = EXUpdatesUpdateStatusReady;
|
||||
}
|
||||
NSString * const updateSql = @"UPDATE updates SET status = ?1, keep = 1 WHERE id = ?2;";
|
||||
[self _executeSql:updateSql
|
||||
withArgs:@[
|
||||
@(update.status),
|
||||
update.updateId
|
||||
]
|
||||
error:error];
|
||||
}
|
||||
|
||||
- (void)setScopeKey:(NSString *)scopeKey onUpdate:(EXUpdatesUpdate *)update error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const updateSql = @"UPDATE updates SET scope_key = ?1 WHERE id = ?2;";
|
||||
[self _executeSql:updateSql withArgs:@[scopeKey, update.updateId] error:error];
|
||||
}
|
||||
|
||||
# pragma mark - delete
|
||||
|
||||
- (void)deleteUpdates:(NSArray<EXUpdatesUpdate *> *)updates error:(NSError ** _Nullable)error
|
||||
{
|
||||
sqlite3_exec(_db, "BEGIN;", NULL, NULL, NULL);
|
||||
|
||||
NSString * const sql = @"DELETE FROM updates WHERE id = ?1;";
|
||||
for (EXUpdatesUpdate *update in updates) {
|
||||
if ([self _executeSql:sql withArgs:@[update.updateId] error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_exec(_db, "COMMIT;", NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
- (nullable NSArray<EXUpdatesAsset *> *)deleteUnusedAssetsWithError:(NSError ** _Nullable)error
|
||||
{
|
||||
// the simplest way to mark the assets we want to delete
|
||||
// is to mark all assets for deletion, then go back and unmark
|
||||
// those assets in updates we want to keep
|
||||
// this is safe as long as we do this inside of a transaction
|
||||
|
||||
sqlite3_exec(_db, "BEGIN;", NULL, NULL, NULL);
|
||||
|
||||
NSString * const update1Sql = @"UPDATE assets SET marked_for_deletion = 1;";
|
||||
if ([self _executeSql:update1Sql withArgs:nil error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * const update2Sql = @"UPDATE assets SET marked_for_deletion = 0 WHERE id IN (\
|
||||
SELECT asset_id \
|
||||
FROM updates_assets \
|
||||
INNER JOIN updates ON updates_assets.update_id = updates.id\
|
||||
WHERE updates.keep = 1\
|
||||
);";
|
||||
if ([self _executeSql:update2Sql withArgs:nil error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString * const selectSql = @"SELECT * FROM assets WHERE marked_for_deletion = 1;";
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:selectSql withArgs:nil error:error];
|
||||
if (!rows) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray *assets = [NSMutableArray new];
|
||||
for (NSDictionary *row in rows) {
|
||||
[assets addObject:[self _assetWithRow:row]];
|
||||
}
|
||||
|
||||
NSString * const deleteSql = @"DELETE FROM assets WHERE marked_for_deletion = 1;";
|
||||
if ([self _executeSql:deleteSql withArgs:nil error:error] == nil) {
|
||||
sqlite3_exec(_db, "ROLLBACK;", NULL, NULL, NULL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
sqlite3_exec(_db, "COMMIT;", NULL, NULL, NULL);
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
# pragma mark - select
|
||||
|
||||
- (nullable NSArray<EXUpdatesUpdate *> *)allUpdatesWithConfig:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const sql = @"SELECT * FROM updates WHERE scope_key = ?1;";
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:sql withArgs:@[config.scopeKey] error:error];
|
||||
if (!rows) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<EXUpdatesUpdate *> *launchableUpdates = [NSMutableArray new];
|
||||
for (NSDictionary *row in rows) {
|
||||
[launchableUpdates addObject:[self _updateWithRow:row config:config]];
|
||||
}
|
||||
return launchableUpdates;
|
||||
}
|
||||
|
||||
- (nullable NSArray<EXUpdatesUpdate *> *)launchableUpdatesWithConfig:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString *sql = [NSString stringWithFormat:@"SELECT *\
|
||||
FROM updates\
|
||||
WHERE scope_key = ?1\
|
||||
AND status IN (%li, %li, %li);", (long)EXUpdatesUpdateStatusReady, (long)EXUpdatesUpdateStatusEmbedded, (long)EXUpdatesUpdateStatusDevelopment];
|
||||
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:sql withArgs:@[config.scopeKey] error:error];
|
||||
if (!rows) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<EXUpdatesUpdate *> *launchableUpdates = [NSMutableArray new];
|
||||
for (NSDictionary *row in rows) {
|
||||
[launchableUpdates addObject:[self _updateWithRow:row config:config]];
|
||||
}
|
||||
return launchableUpdates;
|
||||
}
|
||||
|
||||
- (nullable EXUpdatesUpdate *)updateWithId:(NSUUID *)updateId config:(EXUpdatesConfig *)config error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const sql = @"SELECT *\
|
||||
FROM updates\
|
||||
WHERE updates.id = ?1;";
|
||||
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:sql withArgs:@[updateId] error:error];
|
||||
if (!rows || ![rows count]) {
|
||||
return nil;
|
||||
} else {
|
||||
return [self _updateWithRow:rows[0] config:config];
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSArray<EXUpdatesAsset *> *)assetsWithUpdateId:(NSUUID *)updateId error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const sql = @"SELECT assets.id, \"key\", url, type, relative_path, assets.metadata, launch_asset_id\
|
||||
FROM assets\
|
||||
INNER JOIN updates_assets ON updates_assets.asset_id = assets.id\
|
||||
INNER JOIN updates ON updates_assets.update_id = updates.id\
|
||||
WHERE updates.id = ?1;";
|
||||
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:sql withArgs:@[updateId] error:error];
|
||||
if (!rows) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<EXUpdatesAsset *> *assets = [NSMutableArray arrayWithCapacity:rows.count];
|
||||
|
||||
for (NSDictionary *row in rows) {
|
||||
[assets addObject:[self _assetWithRow:row]];
|
||||
}
|
||||
|
||||
return assets;
|
||||
}
|
||||
|
||||
- (nullable EXUpdatesAsset *)assetWithKey:(NSString *)key error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSString * const sql = @"SELECT * FROM assets WHERE \"key\" = ?1 LIMIT 1;";
|
||||
|
||||
NSArray<NSDictionary *> *rows = [self _executeSql:sql withArgs:@[key] error:error];
|
||||
if (!rows || ![rows count]) {
|
||||
return nil;
|
||||
} else {
|
||||
return [self _assetWithRow:rows[0]];
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - helper methods
|
||||
|
||||
- (nullable NSArray<NSDictionary *> *)_executeSql:(NSString *)sql withArgs:(nullable NSArray *)args error:(NSError ** _Nullable)error
|
||||
{
|
||||
NSAssert(_db, @"Missing database handle");
|
||||
dispatch_assert_queue(_databaseQueue);
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
|
||||
if (error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
if (args) {
|
||||
if (![self _bindStatement:stmt withArgs:args]) {
|
||||
if (error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableArray *rows = [NSMutableArray arrayWithCapacity:0];
|
||||
NSMutableArray *columnNames = [NSMutableArray arrayWithCapacity:0];
|
||||
|
||||
int columnCount = 0;
|
||||
BOOL didFetchColumns = NO;
|
||||
int result;
|
||||
BOOL hasMore = YES;
|
||||
BOOL didError = NO;
|
||||
while (hasMore) {
|
||||
result = sqlite3_step(stmt);
|
||||
switch (result) {
|
||||
case SQLITE_ROW: {
|
||||
if (!didFetchColumns) {
|
||||
// get all column names once at the beginning
|
||||
columnCount = sqlite3_column_count(stmt);
|
||||
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
[columnNames addObject:[NSString stringWithUTF8String:sqlite3_column_name(stmt, i)]];
|
||||
}
|
||||
didFetchColumns = YES;
|
||||
}
|
||||
NSMutableDictionary *entry = [NSMutableDictionary dictionary];
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
id columnValue = [self _getValueWithStatement:stmt column:i];
|
||||
entry[columnNames[i]] = columnValue;
|
||||
}
|
||||
[rows addObject:entry];
|
||||
break;
|
||||
}
|
||||
case SQLITE_DONE:
|
||||
hasMore = NO;
|
||||
break;
|
||||
default:
|
||||
didError = YES;
|
||||
hasMore = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (didError && error != nil) {
|
||||
*error = [self _errorFromSqlite:_db];
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return didError ? nil : rows;
|
||||
}
|
||||
|
||||
- (id)_getValueWithStatement:(sqlite3_stmt *)stmt column:(int)column
|
||||
{
|
||||
int columnType = sqlite3_column_type(stmt, column);
|
||||
switch (columnType) {
|
||||
case SQLITE_INTEGER:
|
||||
return @(sqlite3_column_int64(stmt, column));
|
||||
case SQLITE_FLOAT:
|
||||
return @(sqlite3_column_double(stmt, column));
|
||||
case SQLITE_BLOB:
|
||||
NSAssert(sqlite3_column_bytes(stmt, column) == 16, @"SQLite BLOB value should be a valid UUID");
|
||||
return [[NSUUID alloc] initWithUUIDBytes:sqlite3_column_blob(stmt, column)];
|
||||
case SQLITE_TEXT:
|
||||
return [[NSString alloc] initWithBytes:(char *)sqlite3_column_text(stmt, column)
|
||||
length:sqlite3_column_bytes(stmt, column)
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
return [NSNull null];
|
||||
}
|
||||
|
||||
- (BOOL)_bindStatement:(sqlite3_stmt *)stmt withArgs:(NSArray *)args
|
||||
{
|
||||
__block BOOL success = YES;
|
||||
[args enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj isKindOfClass:[NSUUID class]]) {
|
||||
uuid_t bytes;
|
||||
[((NSUUID *)obj) getUUIDBytes:bytes];
|
||||
if (sqlite3_bind_blob(stmt, (int)idx + 1, bytes, 16, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
success = NO;
|
||||
*stop = YES;
|
||||
}
|
||||
} else if ([obj isKindOfClass:[NSNumber class]]) {
|
||||
if (sqlite3_bind_int64(stmt, (int)idx + 1, [((NSNumber *)obj) longLongValue]) != SQLITE_OK) {
|
||||
success = NO;
|
||||
*stop = YES;
|
||||
}
|
||||
} else if ([obj isKindOfClass:[NSDictionary class]]) {
|
||||
NSError *error;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:(NSDictionary *)obj options:kNilOptions error:&error];
|
||||
if (!error && sqlite3_bind_text(stmt, (int)idx + 1, jsonData.bytes, (int)jsonData.length, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
success = NO;
|
||||
*stop = YES;
|
||||
}
|
||||
} else if ([obj isKindOfClass:[NSNull class]]) {
|
||||
if (sqlite3_bind_null(stmt, (int)idx + 1) != SQLITE_OK) {
|
||||
success = NO;
|
||||
*stop = YES;
|
||||
}
|
||||
} else {
|
||||
// convert to string
|
||||
NSString *string = [obj isKindOfClass:[NSString class]] ? (NSString *)obj : [obj description];
|
||||
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (sqlite3_bind_text(stmt, (int)idx + 1, data.bytes, (int)data.length, SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
success = NO;
|
||||
*stop = YES;
|
||||
}
|
||||
}
|
||||
}];
|
||||
return success;
|
||||
}
|
||||
|
||||
- (NSError *)_errorFromSqlite:(struct sqlite3 *)db
|
||||
{
|
||||
int code = sqlite3_errcode(db);
|
||||
int extendedCode = sqlite3_extended_errcode(db);
|
||||
NSString *message = [NSString stringWithUTF8String:sqlite3_errmsg(db)];
|
||||
return [NSError errorWithDomain:EXUpdatesDatabaseErrorDomain
|
||||
code:extendedCode
|
||||
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Error code %i: %@ (extended error code %i)", code, message, extendedCode]}];
|
||||
}
|
||||
|
||||
- (EXUpdatesUpdate *)_updateWithRow:(NSDictionary *)row config:(EXUpdatesConfig *)config
|
||||
{
|
||||
NSError *error;
|
||||
id metadata = nil;
|
||||
id rowMetadata = row[@"metadata"];
|
||||
if ([rowMetadata isKindOfClass:[NSString class]]) {
|
||||
metadata = [NSJSONSerialization JSONObjectWithData:[(NSString *)rowMetadata dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
|
||||
NSAssert(!error && metadata && [metadata isKindOfClass:[NSDictionary class]], @"Update metadata should be a valid JSON object");
|
||||
}
|
||||
EXUpdatesUpdate *update = [EXUpdatesUpdate updateWithId:row[@"id"]
|
||||
scopeKey:row[@"scope_key"]
|
||||
commitTime:[NSDate dateWithTimeIntervalSince1970:[(NSNumber *)row[@"commit_time"] doubleValue] / 1000]
|
||||
runtimeVersion:row[@"runtime_version"]
|
||||
metadata:metadata
|
||||
status:(EXUpdatesUpdateStatus)[(NSNumber *)row[@"status"] integerValue]
|
||||
keep:[(NSNumber *)row[@"keep"] boolValue]
|
||||
config:config
|
||||
database:self];
|
||||
return update;
|
||||
}
|
||||
|
||||
- (EXUpdatesAsset *)_assetWithRow:(NSDictionary *)row
|
||||
{
|
||||
NSError *error;
|
||||
id metadata = nil;
|
||||
id rowMetadata = row[@"metadata"];
|
||||
if ([rowMetadata isKindOfClass:[NSString class]]) {
|
||||
metadata = [NSJSONSerialization JSONObjectWithData:[(NSString *)rowMetadata dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
|
||||
NSAssert(!error && metadata && [metadata isKindOfClass:[NSDictionary class]], @"Asset metadata should be a valid JSON object");
|
||||
}
|
||||
|
||||
id launchAssetId = row[@"launch_asset_id"];
|
||||
id rowUrl = row[@"url"];
|
||||
NSURL *url;
|
||||
if (rowUrl && [rowUrl isKindOfClass:[NSString class]]) {
|
||||
url = [NSURL URLWithString:rowUrl];
|
||||
}
|
||||
|
||||
EXUpdatesAsset *asset = [[EXUpdatesAsset alloc] initWithKey:row[@"key"] type:row[@"type"]];
|
||||
asset.url = url;
|
||||
asset.downloadTime = [NSDate dateWithTimeIntervalSince1970:([(NSNumber *)row[@"download_time"] doubleValue] / 1000)];
|
||||
asset.filename = row[@"relative_path"];
|
||||
asset.contentHash = row[@"hash"];
|
||||
asset.metadata = metadata;
|
||||
asset.isLaunchAsset = (launchAssetId && [launchAssetId isKindOfClass:[NSNumber class]])
|
||||
? [(NSNumber *)launchAssetId isEqualToNumber:(NSNumber *)row[@"id"]]
|
||||
: NO;
|
||||
return asset;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
20
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesReaper.h
generated
vendored
Normal file
20
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesReaper.h
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesReaper : NSObject
|
||||
|
||||
+ (void)reapUnusedUpdatesWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
launchedUpdate:(EXUpdatesUpdate *)launchedUpdate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
79
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesReaper.m
generated
vendored
Normal file
79
node_modules/expo-updates/ios/EXUpdates/Database/EXUpdatesReaper.m
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
#import <EXUpdates/EXUpdatesReaper.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation EXUpdatesReaper
|
||||
|
||||
+ (void)reapUnusedUpdatesWithConfig:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
directory:(NSURL *)directory
|
||||
selectionPolicy:(id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
launchedUpdate:(EXUpdatesUpdate *)launchedUpdate
|
||||
{
|
||||
dispatch_async(database.databaseQueue, ^{
|
||||
NSError *error;
|
||||
NSDate *beginDeleteFromDatabase = [NSDate date];
|
||||
|
||||
[database markUpdateFinished:launchedUpdate error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error reaping updates: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<EXUpdatesUpdate *> *allUpdates = [database allUpdatesWithConfig:config error:&error];
|
||||
if (!allUpdates || error) {
|
||||
NSLog(@"Error reaping updates: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
NSArray<EXUpdatesUpdate *> *updatesToDelete = [selectionPolicy updatesToDeleteWithLaunchedUpdate:launchedUpdate updates:allUpdates];
|
||||
[database deleteUpdates:updatesToDelete error:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error reaping updates: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<EXUpdatesAsset *> *assetsForDeletion = [database deleteUnusedAssetsWithError:&error];
|
||||
if (error) {
|
||||
NSLog(@"Error reaping updates: %@", error.localizedDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"Deleted assets and updates from SQLite in %f ms", [beginDeleteFromDatabase timeIntervalSinceNow] * -1000);
|
||||
|
||||
dispatch_async([EXUpdatesFileDownloader assetFilesQueue], ^{
|
||||
NSUInteger deletedAssets = 0;
|
||||
NSMutableArray<EXUpdatesAsset *> *erroredAssets = [NSMutableArray new];
|
||||
|
||||
NSDate *beginDeleteAssets = [NSDate date];
|
||||
for (EXUpdatesAsset *asset in assetsForDeletion) {
|
||||
NSURL *localUrl = [directory URLByAppendingPathComponent:asset.filename];
|
||||
NSError *error;
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:localUrl.path] && ![NSFileManager.defaultManager removeItemAtURL:localUrl error:&error]) {
|
||||
NSLog(@"Error deleting asset at %@: %@", localUrl, error.localizedDescription);
|
||||
[erroredAssets addObject:asset];
|
||||
} else {
|
||||
deletedAssets++;
|
||||
}
|
||||
}
|
||||
NSLog(@"Deleted %lu assets from disk in %f ms", (unsigned long)deletedAssets, [beginDeleteAssets timeIntervalSinceNow] * -1000);
|
||||
|
||||
NSDate *beginRetryDeletes = [NSDate date];
|
||||
// retry errored deletions
|
||||
for (EXUpdatesAsset *asset in erroredAssets) {
|
||||
NSURL *localUrl = [directory URLByAppendingPathComponent:asset.filename];
|
||||
NSError *error;
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:localUrl.path] && ![NSFileManager.defaultManager removeItemAtURL:localUrl error:&error]) {
|
||||
NSLog(@"Retried deleting asset at %@ and failed again: %@", localUrl, error.localizedDescription);
|
||||
}
|
||||
}
|
||||
NSLog(@"Retried deleting assets from disk in %f ms", [beginRetryDeletes timeIntervalSinceNow] * -1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
99
node_modules/expo-updates/ios/EXUpdates/EXUpdatesAppController.h
generated
vendored
Normal file
99
node_modules/expo-updates/ios/EXUpdates/EXUpdatesAppController.h
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesAppLoaderTask.h>
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
#import <EXUpdates/EXUpdatesService.h>
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class EXUpdatesAppController;
|
||||
|
||||
@protocol EXUpdatesAppControllerDelegate <NSObject>
|
||||
|
||||
- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success;
|
||||
|
||||
@end
|
||||
|
||||
@interface EXUpdatesAppController : NSObject <EXUpdatesAppLoaderTaskDelegate>
|
||||
|
||||
/**
|
||||
Delegate which will be notified when EXUpdates has an update ready to launch and
|
||||
`launchAssetUrl` is nonnull.
|
||||
*/
|
||||
@property (nonatomic, weak) id<EXUpdatesAppControllerDelegate> delegate;
|
||||
|
||||
/**
|
||||
The RCTBridge for which EXUpdates is providing the JS bundle and assets.
|
||||
This is optional, but required in order for `Updates.reload()` and Updates module events to work.
|
||||
*/
|
||||
@property (nonatomic, weak) RCTBridge *bridge;
|
||||
|
||||
/**
|
||||
The URL on disk to source asset for the RCTBridge.
|
||||
Will be null until the EXUpdatesAppController delegate method is called.
|
||||
This should be provided in the `sourceURLForBridge:` method of RCTBridgeDelegate.
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly, strong) NSURL *launchAssetUrl;
|
||||
/**
|
||||
A dictionary of the locally downloaded assets for the current update. Keys are the remote URLs
|
||||
of the assets and values are local paths. This should be exported by the Updates JS module and
|
||||
can be used by `expo-asset` or a similar module to override React Native's asset resolution and
|
||||
use the locally downloaded assets.
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly, strong) NSDictionary *assetFilesMap;
|
||||
@property (nonatomic, readonly, assign) BOOL isUsingEmbeddedAssets;
|
||||
|
||||
/**
|
||||
for internal use in EXUpdates
|
||||
*/
|
||||
@property (nonatomic, readonly) EXUpdatesConfig *config;
|
||||
@property (nonatomic, readonly) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, readonly) id<EXUpdatesSelectionPolicy> selectionPolicy;
|
||||
@property (nonatomic, readonly) NSURL *updatesDirectory;
|
||||
@property (nonatomic, readonly) dispatch_queue_t assetFilesQueue;
|
||||
@property (nonatomic, readonly, assign) BOOL isStarted;
|
||||
@property (nonatomic, readonly, assign) BOOL isEmergencyLaunch;
|
||||
@property (nullable, nonatomic, readonly, strong) EXUpdatesUpdate *launchedUpdate;
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
Overrides the configuration values specified in Expo.plist with the ones provided in this
|
||||
dictionary. This method can be used if any of these values should be determined at runtime
|
||||
instead of buildtime. If used, this method must be called before any other method on the
|
||||
shared instance of EXUpdatesAppController.
|
||||
*/
|
||||
- (void)setConfiguration:(NSDictionary *)configuration;
|
||||
|
||||
/**
|
||||
Starts the update process to launch a previously-loaded update and (if configured to do so)
|
||||
check for a new update from the server. This method should be called as early as possible in
|
||||
the application's lifecycle.
|
||||
|
||||
Note that iOS may stop showing the app's splash screen in case the update is taking a while
|
||||
to load. If your splash screen setup is simple, you may want to use the
|
||||
`startAndShowLaunchScreen:` method instead.
|
||||
*/
|
||||
- (void)start;
|
||||
|
||||
/**
|
||||
Starts the update process to launch a previously-loaded update and (if configured to do so)
|
||||
check for a new update from the server. This method should be called as early as possible in
|
||||
the application's lifecycle.
|
||||
|
||||
Note that iOS may stop showing the app's splash screen in case the update is taking a while
|
||||
to load. This method will attempt to find `LaunchScreen.xib` and load it into view while the
|
||||
update is loading.
|
||||
*/
|
||||
- (void)startAndShowLaunchScreen:(UIWindow *)window;
|
||||
|
||||
- (void)requestRelaunchWithCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
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
|
34
node_modules/expo-updates/ios/EXUpdates/EXUpdatesConfig.h
generated
vendored
Normal file
34
node_modules/expo-updates/ios/EXUpdates/EXUpdatesConfig.h
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, EXUpdatesCheckAutomaticallyConfig) {
|
||||
EXUpdatesCheckAutomaticallyConfigAlways = 0,
|
||||
EXUpdatesCheckAutomaticallyConfigWifiOnly = 1,
|
||||
EXUpdatesCheckAutomaticallyConfigNever = 2
|
||||
};
|
||||
|
||||
@interface EXUpdatesConfig : NSObject
|
||||
|
||||
@property (nonatomic, readonly) BOOL isEnabled;
|
||||
@property (nonatomic, readonly) NSString *scopeKey;
|
||||
@property (nonatomic, readonly) NSURL *updateUrl;
|
||||
@property (nonatomic, readonly) NSDictionary *requestHeaders;
|
||||
@property (nonatomic, readonly) NSString *releaseChannel;
|
||||
@property (nonatomic, readonly) NSNumber *launchWaitMs;
|
||||
@property (nonatomic, readonly) EXUpdatesCheckAutomaticallyConfig checkOnLaunch;
|
||||
|
||||
@property (nullable, nonatomic, readonly) NSString *sdkVersion;
|
||||
@property (nullable, nonatomic, readonly) NSString *runtimeVersion;
|
||||
|
||||
@property (nonatomic, readonly) BOOL usesLegacyManifest;
|
||||
@property (nonatomic, readonly) BOOL hasEmbeddedUpdate;
|
||||
|
||||
+ (instancetype)configWithDictionary:(NSDictionary *)config;
|
||||
- (void)loadConfigFromDictionary:(NSDictionary *)config;
|
||||
|
||||
+ (NSString *)normalizedURLOrigin:(NSURL *)url;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
172
node_modules/expo-updates/ios/EXUpdates/EXUpdatesConfig.m
generated
vendored
Normal file
172
node_modules/expo-updates/ios/EXUpdates/EXUpdatesConfig.m
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesConfig ()
|
||||
|
||||
@property (nonatomic, readwrite, assign) BOOL isEnabled;
|
||||
@property (nonatomic, readwrite, strong) NSString *scopeKey;
|
||||
@property (nonatomic, readwrite, strong) NSURL *updateUrl;
|
||||
@property (nonatomic, readwrite, strong) NSDictionary *requestHeaders;
|
||||
@property (nonatomic, readwrite, strong) NSString *releaseChannel;
|
||||
@property (nonatomic, readwrite, strong) NSNumber *launchWaitMs;
|
||||
@property (nonatomic, readwrite, assign) EXUpdatesCheckAutomaticallyConfig checkOnLaunch;
|
||||
|
||||
@property (nullable, nonatomic, readwrite, strong) NSString *sdkVersion;
|
||||
@property (nullable, nonatomic, readwrite, strong) NSString *runtimeVersion;
|
||||
|
||||
@end
|
||||
|
||||
static NSString * const EXUpdatesDefaultReleaseChannelName = @"default";
|
||||
|
||||
static NSString * const EXUpdatesConfigEnabledKey = @"EXUpdatesEnabled";
|
||||
static NSString * const EXUpdatesConfigScopeKeyKey = @"EXUpdatesScopeKey";
|
||||
static NSString * const EXUpdatesConfigUpdateUrlKey = @"EXUpdatesURL";
|
||||
static NSString * const EXUpdatesConfigRequestHeadersKey = @"EXUpdatesRequestHeaders";
|
||||
static NSString * const EXUpdatesConfigReleaseChannelKey = @"EXUpdatesReleaseChannel";
|
||||
static NSString * const EXUpdatesConfigLaunchWaitMsKey = @"EXUpdatesLaunchWaitMs";
|
||||
static NSString * const EXUpdatesConfigCheckOnLaunchKey = @"EXUpdatesCheckOnLaunch";
|
||||
static NSString * const EXUpdatesConfigSDKVersionKey = @"EXUpdatesSDKVersion";
|
||||
static NSString * const EXUpdatesConfigRuntimeVersionKey = @"EXUpdatesRuntimeVersion";
|
||||
static NSString * const EXUpdatesConfigUsesLegacyManifestKey = @"EXUpdatesUsesLegacyManifest";
|
||||
static NSString * const EXUpdatesConfigHasEmbeddedUpdateKey = @"EXUpdatesHasEmbeddedUpdate";
|
||||
|
||||
static NSString * const EXUpdatesConfigAlwaysString = @"ALWAYS";
|
||||
static NSString * const EXUpdatesConfigWifiOnlyString = @"WIFI_ONLY";
|
||||
static NSString * const EXUpdatesConfigNeverString = @"NEVER";
|
||||
|
||||
@implementation EXUpdatesConfig
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_isEnabled = YES;
|
||||
_requestHeaders = @{};
|
||||
_releaseChannel = EXUpdatesDefaultReleaseChannelName;
|
||||
_launchWaitMs = @(0);
|
||||
_checkOnLaunch = EXUpdatesCheckAutomaticallyConfigAlways;
|
||||
_usesLegacyManifest = YES;
|
||||
_hasEmbeddedUpdate = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)configWithDictionary:(NSDictionary *)config
|
||||
{
|
||||
EXUpdatesConfig *updatesConfig = [[EXUpdatesConfig alloc] init];
|
||||
[updatesConfig loadConfigFromDictionary:config];
|
||||
return updatesConfig;
|
||||
}
|
||||
|
||||
- (void)loadConfigFromDictionary:(NSDictionary *)config
|
||||
{
|
||||
id isEnabled = config[EXUpdatesConfigEnabledKey];
|
||||
if (isEnabled && [isEnabled isKindOfClass:[NSNumber class]]) {
|
||||
_isEnabled = [(NSNumber *)isEnabled boolValue];
|
||||
}
|
||||
|
||||
id updateUrl = config[EXUpdatesConfigUpdateUrlKey];
|
||||
if (updateUrl && [updateUrl isKindOfClass:[NSString class]]) {
|
||||
NSURL *url = [NSURL URLWithString:(NSString *)updateUrl];
|
||||
_updateUrl = url;
|
||||
}
|
||||
|
||||
id scopeKey = config[EXUpdatesConfigScopeKeyKey];
|
||||
if (scopeKey && [scopeKey isKindOfClass:[NSString class]]) {
|
||||
_scopeKey = (NSString *)scopeKey;
|
||||
}
|
||||
|
||||
// set updateUrl as the default value if none is provided
|
||||
if (!_scopeKey) {
|
||||
if (_updateUrl) {
|
||||
_scopeKey = [[self class] normalizedURLOrigin:_updateUrl];
|
||||
} else {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:@"expo-updates must be configured with a valid update URL or scope key."
|
||||
userInfo:@{}];
|
||||
}
|
||||
}
|
||||
|
||||
id requestHeaders = config[EXUpdatesConfigRequestHeadersKey];
|
||||
if (requestHeaders && [requestHeaders isKindOfClass:[NSDictionary class]]) {
|
||||
_requestHeaders = (NSDictionary *)requestHeaders;
|
||||
}
|
||||
|
||||
id releaseChannel = config[EXUpdatesConfigReleaseChannelKey];
|
||||
if (releaseChannel && [releaseChannel isKindOfClass:[NSString class]]) {
|
||||
_releaseChannel = (NSString *)releaseChannel;
|
||||
}
|
||||
|
||||
id launchWaitMs = config[EXUpdatesConfigLaunchWaitMsKey];
|
||||
if (launchWaitMs && [launchWaitMs isKindOfClass:[NSNumber class]]) {
|
||||
_launchWaitMs = (NSNumber *)launchWaitMs;
|
||||
} else if (launchWaitMs && [launchWaitMs isKindOfClass:[NSString class]]) {
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
formatter.numberStyle = NSNumberFormatterNoStyle;
|
||||
_launchWaitMs = [formatter numberFromString:(NSString *)launchWaitMs];
|
||||
}
|
||||
|
||||
id checkOnLaunch = config[EXUpdatesConfigCheckOnLaunchKey];
|
||||
if (checkOnLaunch && [checkOnLaunch isKindOfClass:[NSString class]]) {
|
||||
if ([EXUpdatesConfigNeverString isEqualToString:(NSString *)checkOnLaunch]) {
|
||||
_checkOnLaunch = EXUpdatesCheckAutomaticallyConfigNever;
|
||||
} else if ([EXUpdatesConfigWifiOnlyString isEqualToString:(NSString *)checkOnLaunch]) {
|
||||
_checkOnLaunch = EXUpdatesCheckAutomaticallyConfigWifiOnly;
|
||||
} else if ([EXUpdatesConfigAlwaysString isEqualToString:(NSString *)checkOnLaunch]) {
|
||||
_checkOnLaunch = EXUpdatesCheckAutomaticallyConfigAlways;
|
||||
}
|
||||
}
|
||||
|
||||
id sdkVersion = config[EXUpdatesConfigSDKVersionKey];
|
||||
if (sdkVersion && [sdkVersion isKindOfClass:[NSString class]]) {
|
||||
_sdkVersion = (NSString *)sdkVersion;
|
||||
}
|
||||
|
||||
id runtimeVersion = config[EXUpdatesConfigRuntimeVersionKey];
|
||||
if (runtimeVersion && [runtimeVersion isKindOfClass:[NSString class]]) {
|
||||
_runtimeVersion = (NSString *)runtimeVersion;
|
||||
}
|
||||
|
||||
NSAssert(_sdkVersion || _runtimeVersion, @"One of EXUpdatesSDKVersion or EXUpdatesRuntimeVersion must be configured in expo-updates");
|
||||
|
||||
id usesLegacyManifest = config[EXUpdatesConfigUsesLegacyManifestKey];
|
||||
if (usesLegacyManifest && [usesLegacyManifest isKindOfClass:[NSNumber class]]) {
|
||||
_usesLegacyManifest = [(NSNumber *)usesLegacyManifest boolValue];
|
||||
}
|
||||
|
||||
id hasEmbeddedUpdate = config[EXUpdatesConfigHasEmbeddedUpdateKey];
|
||||
if (hasEmbeddedUpdate && [hasEmbeddedUpdate isKindOfClass:[NSNumber class]]) {
|
||||
_hasEmbeddedUpdate = [(NSNumber *)hasEmbeddedUpdate boolValue];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)normalizedURLOrigin:(NSURL *)url
|
||||
{
|
||||
NSString *scheme = url.scheme;
|
||||
NSNumber *port = url.port;
|
||||
if (port && port.integerValue > -1 && [port isEqual:[[self class] defaultPortForScheme:scheme]]) {
|
||||
port = nil;
|
||||
}
|
||||
|
||||
return (port && port.integerValue > -1)
|
||||
? [NSString stringWithFormat:@"%@://%@:%ld", scheme, url.host, (long)port.integerValue]
|
||||
: [NSString stringWithFormat:@"%@://%@", scheme, url.host];
|
||||
}
|
||||
|
||||
+ (nullable NSNumber *)defaultPortForScheme:(NSString *)scheme
|
||||
{
|
||||
if ([@"http" isEqualToString:scheme] || [@"ws" isEqualToString:scheme]) {
|
||||
return @(80);
|
||||
} else if ([@"https" isEqualToString:scheme] || [@"wss" isEqualToString:scheme]) {
|
||||
return @(443);
|
||||
} else if ([@"ftp" isEqualToString:scheme]) {
|
||||
return @(21);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
7
node_modules/expo-updates/ios/EXUpdates/EXUpdatesModule.h
generated
vendored
Normal file
7
node_modules/expo-updates/ios/EXUpdates/EXUpdatesModule.h
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <UMCore/UMExportedModule.h>
|
||||
#import <UMCore/UMModuleRegistryConsumer.h>
|
||||
|
||||
@interface EXUpdatesModule : UMExportedModule <UMModuleRegistryConsumer>
|
||||
@end
|
129
node_modules/expo-updates/ios/EXUpdates/EXUpdatesModule.m
generated
vendored
Normal file
129
node_modules/expo-updates/ios/EXUpdates/EXUpdatesModule.m
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesFileDownloader.h>
|
||||
#import <EXUpdates/EXUpdatesModule.h>
|
||||
#import <EXUpdates/EXUpdatesRemoteAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesService.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
@interface EXUpdatesModule ()
|
||||
|
||||
@property (nonatomic, weak) id<EXUpdatesInterface> updatesService;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesModule
|
||||
|
||||
UM_EXPORT_MODULE(ExpoUpdates);
|
||||
|
||||
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
|
||||
{
|
||||
_updatesService = [moduleRegistry getModuleImplementingProtocol:@protocol(EXUpdatesInterface)];
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
if (!_updatesService.isStarted) {
|
||||
return @{
|
||||
@"isEnabled": @(NO)
|
||||
};
|
||||
}
|
||||
EXUpdatesUpdate *launchedUpdate = _updatesService.launchedUpdate;
|
||||
if (!launchedUpdate) {
|
||||
return @{
|
||||
@"isEnabled": @(NO)
|
||||
};
|
||||
} else {
|
||||
return @{
|
||||
@"isEnabled": @(YES),
|
||||
@"isUsingEmbeddedAssets": @(_updatesService.isUsingEmbeddedAssets),
|
||||
@"updateId": launchedUpdate.updateId.UUIDString ?: @"",
|
||||
@"manifest": launchedUpdate.rawManifest ?: @{},
|
||||
@"releaseChannel": _updatesService.config.releaseChannel,
|
||||
@"localAssets": _updatesService.assetFilesMap ?: @{},
|
||||
@"isEmergencyLaunch": @(_updatesService.isEmergencyLaunch)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(reload,
|
||||
reloadAsync:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (!_updatesService.canRelaunch) {
|
||||
reject(@"ERR_UPDATES_DISABLED", @"The updates module controller has not been properly initialized. If you're in development mode, you cannot use this method. Otherwise, make sure you have called [[EXUpdatesAppController sharedInstance] start].", nil);
|
||||
return;
|
||||
}
|
||||
|
||||
[_updatesService requestRelaunchWithCompletion:^(BOOL success) {
|
||||
if (success) {
|
||||
resolve(nil);
|
||||
} else {
|
||||
reject(@"ERR_UPDATES_RELOAD", @"Could not reload application. Ensure you have set the `bridge` property of EXUpdatesAppController.", nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(checkForUpdateAsync,
|
||||
checkForUpdateAsync:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (!_updatesService.isStarted) {
|
||||
reject(@"ERR_UPDATES_DISABLED", @"The updates module controller has not been properly initialized. If you're in development mode, you cannot check for updates. Otherwise, make sure you have called [[EXUpdatesAppController sharedInstance] start].", nil);
|
||||
return;
|
||||
}
|
||||
|
||||
EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:_updatesService.config];
|
||||
[fileDownloader downloadManifestFromURL:_updatesService.config.updateUrl
|
||||
withDatabase:_updatesService.database
|
||||
cacheDirectory:_updatesService.directory
|
||||
successBlock:^(EXUpdatesUpdate *update) {
|
||||
EXUpdatesUpdate *launchedUpdate = self->_updatesService.launchedUpdate;
|
||||
id<EXUpdatesSelectionPolicy> selectionPolicy = self->_updatesService.selectionPolicy;
|
||||
if ([selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:launchedUpdate]) {
|
||||
resolve(@{
|
||||
@"isAvailable": @(YES),
|
||||
@"manifest": update.rawManifest
|
||||
});
|
||||
} else {
|
||||
resolve(@{
|
||||
@"isAvailable": @(NO)
|
||||
});
|
||||
}
|
||||
} errorBlock:^(NSError *error, NSURLResponse *response) {
|
||||
reject(@"ERR_UPDATES_CHECK", error.localizedDescription, error);
|
||||
}];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(fetchUpdateAsync,
|
||||
fetchUpdateAsync:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (!_updatesService.isStarted) {
|
||||
reject(@"ERR_UPDATES_DISABLED", @"The updates module controller has not been properly initialized. If you're in development mode, you cannot fetch updates. Otherwise, make sure you have called [[EXUpdatesAppController sharedInstance] start].", nil);
|
||||
return;
|
||||
}
|
||||
|
||||
EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_updatesService.config database:_updatesService.database directory:_updatesService.directory completionQueue:self.methodQueue];
|
||||
[remoteAppLoader loadUpdateFromUrl:_updatesService.config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) {
|
||||
return [self->_updatesService.selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:self->_updatesService.launchedUpdate];
|
||||
} success:^(EXUpdatesUpdate * _Nullable update) {
|
||||
if (update) {
|
||||
resolve(@{
|
||||
@"isNew": @(YES),
|
||||
@"manifest": update.rawManifest
|
||||
});
|
||||
} else {
|
||||
resolve(@{
|
||||
@"isNew": @(NO)
|
||||
});
|
||||
}
|
||||
} error:^(NSError * _Nonnull error) {
|
||||
reject(@"ERR_UPDATES_FETCH", @"Failed to download new update", error);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
35
node_modules/expo-updates/ios/EXUpdates/EXUpdatesService.h
generated
vendored
Normal file
35
node_modules/expo-updates/ios/EXUpdates/EXUpdatesService.h
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2020-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesSelectionPolicy.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
#import <UMCore/UMInternalModule.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^EXUpdatesAppRelaunchCompletionBlock)(BOOL success);
|
||||
|
||||
@protocol EXUpdatesInterface
|
||||
|
||||
@property (nonatomic, readonly) EXUpdatesConfig *config;
|
||||
@property (nonatomic, readonly) EXUpdatesDatabase *database;
|
||||
@property (nonatomic, readonly) id<EXUpdatesSelectionPolicy> selectionPolicy;
|
||||
@property (nonatomic, readonly) NSURL *directory;
|
||||
|
||||
@property (nullable, nonatomic, readonly, strong) EXUpdatesUpdate *launchedUpdate;
|
||||
@property (nullable, nonatomic, readonly, strong) NSDictionary *assetFilesMap;
|
||||
@property (nonatomic, readonly, assign) BOOL isUsingEmbeddedAssets;
|
||||
@property (nonatomic, readonly, assign) BOOL isStarted;
|
||||
@property (nonatomic, readonly, assign) BOOL isEmergencyLaunch;
|
||||
@property (nonatomic, readonly, assign) BOOL canRelaunch;
|
||||
|
||||
- (void)requestRelaunchWithCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion;
|
||||
|
||||
@end
|
||||
|
||||
@interface EXUpdatesService : NSObject <UMInternalModule, EXUpdatesInterface>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
75
node_modules/expo-updates/ios/EXUpdates/EXUpdatesService.m
generated
vendored
Normal file
75
node_modules/expo-updates/ios/EXUpdates/EXUpdatesService.m
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2020-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAppController.h>
|
||||
#import <EXUpdates/EXUpdatesService.h>
|
||||
#import <UMCore/UMUtilities.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation EXUpdatesService
|
||||
|
||||
UM_REGISTER_MODULE();
|
||||
|
||||
+ (const NSArray<Protocol *> *)exportedInterfaces
|
||||
{
|
||||
return @[@protocol(EXUpdatesInterface)];
|
||||
}
|
||||
|
||||
- (EXUpdatesConfig *)config
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.config;
|
||||
}
|
||||
|
||||
- (EXUpdatesDatabase *)database
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.database;
|
||||
}
|
||||
|
||||
- (id<EXUpdatesSelectionPolicy>)selectionPolicy
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.selectionPolicy;
|
||||
}
|
||||
|
||||
- (NSURL *)directory
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.updatesDirectory;
|
||||
}
|
||||
|
||||
- (nullable EXUpdatesUpdate *)launchedUpdate
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.launchedUpdate;
|
||||
}
|
||||
|
||||
- (nullable NSDictionary *)assetFilesMap
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.assetFilesMap;
|
||||
}
|
||||
|
||||
- (BOOL)isUsingEmbeddedAssets
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.isUsingEmbeddedAssets;
|
||||
}
|
||||
|
||||
- (BOOL)isStarted
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.isStarted;
|
||||
}
|
||||
|
||||
- (BOOL)isEmergencyLaunch
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.isEmergencyLaunch;
|
||||
}
|
||||
|
||||
- (BOOL)canRelaunch
|
||||
{
|
||||
return EXUpdatesAppController.sharedInstance.isStarted;
|
||||
}
|
||||
|
||||
- (void)requestRelaunchWithCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion
|
||||
{
|
||||
return [EXUpdatesAppController.sharedInstance requestRelaunchWithCompletion:completion];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
20
node_modules/expo-updates/ios/EXUpdates/EXUpdatesUtils.h
generated
vendored
Normal file
20
node_modules/expo-updates/ios/EXUpdates/EXUpdatesUtils.h
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesUtils : NSObject
|
||||
|
||||
+ (void)runBlockOnMainThread:(void (^)(void))block;
|
||||
+ (NSString *)sha256WithData:(NSData *)data;
|
||||
+ (nullable NSURL *)initializeUpdatesDirectoryWithError:(NSError ** _Nullable)error;
|
||||
+ (void)sendEventToBridge:(nullable RCTBridge *)bridge withType:(NSString *)eventType body:(NSDictionary *)body;
|
||||
+ (BOOL)shouldCheckForUpdateWithConfig:(EXUpdatesConfig *)config;
|
||||
+ (NSString *)getRuntimeVersionWithConfig:(EXUpdatesConfig *)config;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
106
node_modules/expo-updates/ios/EXUpdates/EXUpdatesUtils.m
generated
vendored
Normal file
106
node_modules/expo-updates/ios/EXUpdates/EXUpdatesUtils.m
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import <arpa/inet.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesEventName = @"Expo.nativeUpdatesEvent";
|
||||
static NSString * const EXUpdatesUtilsErrorDomain = @"EXUpdatesUtils";
|
||||
|
||||
@implementation EXUpdatesUtils
|
||||
|
||||
+ (void)runBlockOnMainThread:(void (^)(void))block
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)sha256WithData:(NSData *)data
|
||||
{
|
||||
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
|
||||
|
||||
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++)
|
||||
{
|
||||
[output appendFormat:@"%02x", digest[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
+ (nullable NSURL *)initializeUpdatesDirectoryWithError:(NSError ** _Nullable)error
|
||||
{
|
||||
NSFileManager *fileManager = NSFileManager.defaultManager;
|
||||
NSURL *applicationDocumentsDirectory = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
NSURL *updatesDirectory = [applicationDocumentsDirectory URLByAppendingPathComponent:@".expo-internal"];
|
||||
NSString *updatesDirectoryPath = [updatesDirectory path];
|
||||
|
||||
BOOL isDir;
|
||||
BOOL exists = [fileManager fileExistsAtPath:updatesDirectoryPath isDirectory:&isDir];
|
||||
if (exists) {
|
||||
if (!isDir) {
|
||||
*error = [NSError errorWithDomain:EXUpdatesUtilsErrorDomain code:1005 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create the Updates Directory; a file already exists with the required directory name"}];
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
NSError *err;
|
||||
BOOL wasCreated = [fileManager createDirectoryAtPath:updatesDirectoryPath withIntermediateDirectories:YES attributes:nil error:&err];
|
||||
if (!wasCreated) {
|
||||
*error = err;
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return updatesDirectory;
|
||||
}
|
||||
|
||||
+ (void)sendEventToBridge:(nullable RCTBridge *)bridge withType:(NSString *)eventType body:(NSDictionary *)body
|
||||
{
|
||||
if (bridge) {
|
||||
NSMutableDictionary *mutableBody = [body mutableCopy];
|
||||
mutableBody[@"type"] = eventType;
|
||||
[bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[EXUpdatesEventName, mutableBody]];
|
||||
} else {
|
||||
NSLog(@"EXUpdates: Could not emit %@ event. Did you set the bridge property on the controller singleton?", eventType);
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)shouldCheckForUpdateWithConfig:(EXUpdatesConfig *)config
|
||||
{
|
||||
switch (config.checkOnLaunch) {
|
||||
case EXUpdatesCheckAutomaticallyConfigNever:
|
||||
return NO;
|
||||
case EXUpdatesCheckAutomaticallyConfigWifiOnly: {
|
||||
struct sockaddr_in zeroAddress;
|
||||
bzero(&zeroAddress, sizeof(zeroAddress));
|
||||
zeroAddress.sin_len = sizeof(zeroAddress);
|
||||
zeroAddress.sin_family = AF_INET;
|
||||
|
||||
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *) &zeroAddress);
|
||||
SCNetworkReachabilityFlags flags;
|
||||
SCNetworkReachabilityGetFlags(reachability, &flags);
|
||||
|
||||
return (flags & kSCNetworkReachabilityFlagsIsWWAN) == 0;
|
||||
}
|
||||
case EXUpdatesCheckAutomaticallyConfigAlways:
|
||||
default:
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)getRuntimeVersionWithConfig:(EXUpdatesConfig *)config
|
||||
{
|
||||
return config.runtimeVersion ?: config.sdkVersion;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
15
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesBareUpdate.h
generated
vendored
Normal file
15
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesBareUpdate.h
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesBareUpdate : NSObject
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithBareManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
84
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesBareUpdate.m
generated
vendored
Normal file
84
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesBareUpdate.m
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesBareUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate+Private.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation EXUpdatesBareUpdate
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithBareManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
{
|
||||
EXUpdatesUpdate *update = [[EXUpdatesUpdate alloc] initWithRawManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
|
||||
id updateId = manifest[@"id"];
|
||||
id commitTime = manifest[@"commitTime"];
|
||||
id metadata = manifest[@"metadata"];
|
||||
id assets = manifest[@"assets"];
|
||||
|
||||
NSAssert([updateId isKindOfClass:[NSString class]], @"update ID should be a string");
|
||||
NSAssert([commitTime isKindOfClass:[NSNumber class]], @"commitTime should be a number");
|
||||
NSAssert(!metadata || [metadata isKindOfClass:[NSDictionary class]], @"metadata should be null or an object");
|
||||
NSAssert(assets && [assets isKindOfClass:[NSArray class]], @"assets should be a nonnull array");
|
||||
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:(NSString *)updateId];
|
||||
NSAssert(uuid, @"update ID should be a valid UUID");
|
||||
|
||||
NSMutableArray<EXUpdatesAsset *> *processedAssets = [NSMutableArray new];
|
||||
|
||||
NSString *bundleKey = [NSString stringWithFormat:@"bundle-%@", commitTime];
|
||||
EXUpdatesAsset *jsBundleAsset = [[EXUpdatesAsset alloc] initWithKey:bundleKey type:EXUpdatesBareEmbeddedBundleFileType];
|
||||
jsBundleAsset.isLaunchAsset = YES;
|
||||
jsBundleAsset.mainBundleFilename = EXUpdatesBareEmbeddedBundleFilename;
|
||||
[processedAssets addObject:jsBundleAsset];
|
||||
|
||||
for (NSDictionary *assetDict in (NSArray *)assets) {
|
||||
NSAssert([assetDict isKindOfClass:[NSDictionary class]], @"assets must be objects");
|
||||
id packagerHash = assetDict[@"packagerHash"];
|
||||
id type = assetDict[@"type"];
|
||||
id mainBundleDir = assetDict[@"nsBundleDir"];
|
||||
id mainBundleFilename = assetDict[@"nsBundleFilename"];
|
||||
NSAssert(packagerHash && [packagerHash isKindOfClass:[NSString class]], @"asset key should be a nonnull string");
|
||||
NSAssert(type && [type isKindOfClass:[NSString class]], @"asset type should be a nonnull string");
|
||||
NSAssert(mainBundleFilename && [mainBundleFilename isKindOfClass:[NSString class]], @"asset nsBundleFilename should be a nonnull string");
|
||||
if (mainBundleDir) {
|
||||
NSAssert([mainBundleDir isKindOfClass:[NSString class]], @"asset nsBundleDir should be a string");
|
||||
}
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"%@.%@", packagerHash, type];
|
||||
EXUpdatesAsset *asset = [[EXUpdatesAsset alloc] initWithKey:key type:(NSString *)type];
|
||||
asset.mainBundleDir = mainBundleDir;
|
||||
asset.mainBundleFilename = mainBundleFilename;
|
||||
|
||||
[processedAssets addObject:asset];
|
||||
}
|
||||
|
||||
update.updateId = uuid;
|
||||
update.commitTime = [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)commitTime doubleValue] / 1000];
|
||||
update.runtimeVersion = [EXUpdatesUtils getRuntimeVersionWithConfig:config];
|
||||
if (metadata) {
|
||||
update.metadata = (NSDictionary *)metadata;
|
||||
}
|
||||
update.status = EXUpdatesUpdateStatusEmbedded;
|
||||
update.keep = YES;
|
||||
update.assets = processedAssets;
|
||||
|
||||
if ([update.runtimeVersion containsString:@","]) {
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:@"Should not be initializing EXUpdatesBareUpdate in an environment with multiple runtime versions."
|
||||
userInfo:@{}];
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
17
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesLegacyUpdate.h
generated
vendored
Normal file
17
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesLegacyUpdate.h
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesLegacyUpdate : NSObject
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithLegacyManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database;
|
||||
|
||||
+ (NSURL *)bundledAssetBaseUrlWithManifest:(NSDictionary *)manifest config:(EXUpdatesConfig *)config;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
158
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesLegacyUpdate.m
generated
vendored
Normal file
158
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesLegacyUpdate.m
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesLegacyUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate+Private.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static NSString * const EXUpdatesExpoAssetBaseUrl = @"https://d1wp6m56sqw74a.cloudfront.net/~assets/";
|
||||
static NSString * const EXUpdatesExpoIoDomain = @"expo.io";
|
||||
static NSString * const EXUpdatesExpHostDomain = @"exp.host";
|
||||
static NSString * const EXUpdatesExpoTestDomain = @"expo.test";
|
||||
|
||||
@implementation EXUpdatesLegacyUpdate
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithLegacyManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
{
|
||||
EXUpdatesUpdate *update = [[EXUpdatesUpdate alloc] initWithRawManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
|
||||
if ([[self class] areDevToolsEnabledWithManifest:manifest]) {
|
||||
// XDL does not set a releaseId or commitTime for development manifests.
|
||||
// we do not need these so we just stub them out
|
||||
update.updateId = [NSUUID UUID];
|
||||
update.commitTime = [NSDate date];
|
||||
} else {
|
||||
id updateId = manifest[@"releaseId"];
|
||||
NSAssert([updateId isKindOfClass:[NSString class]], @"update ID should be a string");
|
||||
update.updateId = [[NSUUID alloc] initWithUUIDString:(NSString *)updateId];
|
||||
NSAssert(update.updateId, @"update ID should be a valid UUID");
|
||||
|
||||
id commitTimeString = manifest[@"commitTime"];
|
||||
NSAssert([commitTimeString isKindOfClass:[NSString class]], @"commitTime should be a string");
|
||||
update.commitTime = [RCTConvert NSDate:commitTimeString];
|
||||
}
|
||||
|
||||
if ([[self class] isDevelopmentModeManifest:manifest]) {
|
||||
update.isDevelopmentMode = YES;
|
||||
update.status = EXUpdatesUpdateStatusDevelopment;
|
||||
} else {
|
||||
update.status = EXUpdatesUpdateStatusPending;
|
||||
}
|
||||
|
||||
id bundleUrlString = manifest[@"bundleUrl"];
|
||||
id assets = manifest[@"bundledAssets"] ?: @[];
|
||||
|
||||
id sdkVersion = manifest[@"sdkVersion"];
|
||||
id runtimeVersion = manifest[@"runtimeVersion"];
|
||||
if (runtimeVersion && [runtimeVersion isKindOfClass:[NSDictionary class]]) {
|
||||
id runtimeVersionIos = ((NSDictionary *)runtimeVersion)[@"ios"];
|
||||
NSAssert([runtimeVersionIos isKindOfClass:[NSString class]], @"runtimeVersion['ios'] should be a string");
|
||||
update.runtimeVersion = (NSString *)runtimeVersionIos;
|
||||
} else if (runtimeVersion && [runtimeVersion isKindOfClass:[NSString class]]) {
|
||||
update.runtimeVersion = (NSString *)runtimeVersion;
|
||||
} else {
|
||||
NSAssert([sdkVersion isKindOfClass:[NSString class]], @"sdkVersion should be a string");
|
||||
update.runtimeVersion = (NSString *)sdkVersion;
|
||||
}
|
||||
|
||||
NSAssert([bundleUrlString isKindOfClass:[NSString class]], @"bundleUrl should be a string");
|
||||
NSAssert([assets isKindOfClass:[NSArray class]], @"assets should be a nonnull array");
|
||||
|
||||
NSURL *bundleUrl = [NSURL URLWithString:bundleUrlString];
|
||||
NSAssert(bundleUrl, @"bundleUrl should be a valid URL");
|
||||
|
||||
NSMutableArray<EXUpdatesAsset *> *processedAssets = [NSMutableArray new];
|
||||
|
||||
NSString *bundleKey = [NSString stringWithFormat:@"bundle-%@", [EXUpdatesUtils sha256WithData:[(NSString *)bundleUrlString dataUsingEncoding:NSUTF8StringEncoding]]];
|
||||
EXUpdatesAsset *jsBundleAsset = [[EXUpdatesAsset alloc] initWithKey:bundleKey type:EXUpdatesEmbeddedBundleFileType];
|
||||
jsBundleAsset.url = bundleUrl;
|
||||
jsBundleAsset.isLaunchAsset = YES;
|
||||
jsBundleAsset.mainBundleFilename = EXUpdatesEmbeddedBundleFilename;
|
||||
[processedAssets addObject:jsBundleAsset];
|
||||
|
||||
NSURL *bundledAssetBaseUrl = [[self class] bundledAssetBaseUrlWithManifest:manifest config:config];
|
||||
|
||||
for (NSString *bundledAsset in (NSArray *)assets) {
|
||||
NSAssert([bundledAsset isKindOfClass:[NSString class]], @"bundledAssets must be an array of strings");
|
||||
|
||||
NSRange extensionStartRange = [bundledAsset rangeOfString:@"." options:NSBackwardsSearch];
|
||||
NSUInteger prefixLength = [@"asset_" length];
|
||||
NSString *filename;
|
||||
NSString *hash;
|
||||
NSString *type;
|
||||
if (extensionStartRange.location == NSNotFound) {
|
||||
filename = bundledAsset;
|
||||
hash = [bundledAsset substringFromIndex:prefixLength];
|
||||
type = @"";
|
||||
} else {
|
||||
filename = [bundledAsset substringToIndex:extensionStartRange.location];
|
||||
NSRange hashRange = NSMakeRange(prefixLength, extensionStartRange.location - prefixLength);
|
||||
hash = [bundledAsset substringWithRange:hashRange];
|
||||
type = [bundledAsset substringFromIndex:extensionStartRange.location + 1];
|
||||
}
|
||||
|
||||
NSURL *url = [bundledAssetBaseUrl URLByAppendingPathComponent:hash];
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"%@.%@", hash, type];
|
||||
EXUpdatesAsset *asset = [[EXUpdatesAsset alloc] initWithKey:key type:(NSString *)type];
|
||||
asset.url = url;
|
||||
asset.mainBundleFilename = filename;
|
||||
|
||||
[processedAssets addObject:asset];
|
||||
}
|
||||
|
||||
update.metadata = manifest;
|
||||
update.keep = YES;
|
||||
update.bundleUrl = bundleUrl;
|
||||
update.assets = processedAssets;
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
+ (NSURL *)bundledAssetBaseUrlWithManifest:(NSDictionary *)manifest config:(EXUpdatesConfig *)config
|
||||
{
|
||||
NSURL *manifestUrl = config.updateUrl;
|
||||
NSString *host = manifestUrl.host;
|
||||
if (!host ||
|
||||
[host containsString:EXUpdatesExpoIoDomain] ||
|
||||
[host containsString:EXUpdatesExpHostDomain] ||
|
||||
[host containsString:EXUpdatesExpoTestDomain]) {
|
||||
return [NSURL URLWithString:EXUpdatesExpoAssetBaseUrl];
|
||||
} else {
|
||||
NSString *assetsPathOrUrl = manifest[@"assetUrlOverride"] ?: @"assets";
|
||||
// assetUrlOverride may be an absolute or relative URL
|
||||
// if relative, we should resolve with respect to the manifest URL
|
||||
NSURL *maybeAssetsUrl = [NSURL URLWithString:assetsPathOrUrl];
|
||||
if (maybeAssetsUrl && maybeAssetsUrl.scheme) {
|
||||
return maybeAssetsUrl;
|
||||
} else if (maybeAssetsUrl && maybeAssetsUrl.standardizedURL) {
|
||||
return [manifestUrl.URLByDeletingLastPathComponent URLByAppendingPathComponent:maybeAssetsUrl.standardizedURL.relativeString];
|
||||
} else {
|
||||
return [manifestUrl.URLByDeletingLastPathComponent URLByAppendingPathComponent:assetsPathOrUrl];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isDevelopmentModeManifest:(NSDictionary *)manifest
|
||||
{
|
||||
NSDictionary *manifestPackagerOptsConfig = manifest[@"packagerOpts"];
|
||||
return (manifest[@"developer"] != nil && manifestPackagerOptsConfig != nil && [@(YES) isEqualToNumber:manifestPackagerOptsConfig[@"dev"]]);
|
||||
}
|
||||
|
||||
+ (BOOL)areDevToolsEnabledWithManifest:(NSDictionary *)manifest
|
||||
{
|
||||
NSDictionary *manifestDeveloperConfig = manifest[@"developer"];
|
||||
BOOL isDeployedFromTool = (manifestDeveloperConfig && manifestDeveloperConfig[@"tool"] != nil);
|
||||
return (isDeployedFromTool);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
15
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesNewUpdate.h
generated
vendored
Normal file
15
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesNewUpdate.h
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesNewUpdate : NSObject
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithNewManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
93
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesNewUpdate.m
generated
vendored
Normal file
93
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesNewUpdate.m
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
|
||||
#import <EXUpdates/EXUpdatesNewUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate+Private.h>
|
||||
#import <EXUpdates/EXUpdatesUtils.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation EXUpdatesNewUpdate
|
||||
|
||||
+ (EXUpdatesUpdate *)updateWithNewManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
{
|
||||
EXUpdatesUpdate *update = [[EXUpdatesUpdate alloc] initWithRawManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
|
||||
id updateId = manifest[@"id"];
|
||||
id commitTime = manifest[@"commitTime"];
|
||||
id runtimeVersion = manifest[@"runtimeVersion"];
|
||||
id metadata = manifest[@"metadata"];
|
||||
id bundleUrlString = manifest[@"bundleUrl"];
|
||||
id assets = manifest[@"assets"];
|
||||
|
||||
NSAssert([updateId isKindOfClass:[NSString class]], @"update ID should be a string");
|
||||
NSAssert([commitTime isKindOfClass:[NSNumber class]], @"commitTime should be a number");
|
||||
NSAssert([runtimeVersion isKindOfClass:[NSString class]], @"runtimeVersion should be a string");
|
||||
NSAssert(!metadata || [metadata isKindOfClass:[NSDictionary class]], @"metadata should be null or an object");
|
||||
NSAssert([bundleUrlString isKindOfClass:[NSString class]], @"bundleUrl should be a string");
|
||||
NSAssert(assets && [assets isKindOfClass:[NSArray class]], @"assets should be a nonnull array");
|
||||
|
||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:(NSString *)updateId];
|
||||
NSAssert(uuid, @"update ID should be a valid UUID");
|
||||
NSURL *bundleUrl = [NSURL URLWithString:bundleUrlString];
|
||||
NSAssert(bundleUrl, @"bundleUrl should be a valid URL");
|
||||
|
||||
NSMutableArray<EXUpdatesAsset *> *processedAssets = [NSMutableArray new];
|
||||
|
||||
NSString *bundleKey = [NSString stringWithFormat:@"bundle-%@", commitTime];
|
||||
EXUpdatesAsset *jsBundleAsset = [[EXUpdatesAsset alloc] initWithKey:bundleKey type:EXUpdatesEmbeddedBundleFileType];
|
||||
jsBundleAsset.url = bundleUrl;
|
||||
jsBundleAsset.isLaunchAsset = YES;
|
||||
jsBundleAsset.mainBundleFilename = EXUpdatesEmbeddedBundleFilename;
|
||||
[processedAssets addObject:jsBundleAsset];
|
||||
|
||||
for (NSDictionary *assetDict in (NSArray *)assets) {
|
||||
NSAssert([assetDict isKindOfClass:[NSDictionary class]], @"assets must be objects");
|
||||
id key = assetDict[@"key"];
|
||||
id urlString = assetDict[@"url"];
|
||||
id type = assetDict[@"type"];
|
||||
id metadata = assetDict[@"metadata"];
|
||||
id mainBundleFilename = assetDict[@"mainBundleFilename"];
|
||||
NSAssert(key && [key isKindOfClass:[NSString class]], @"asset key should be a nonnull string");
|
||||
NSAssert(urlString && [urlString isKindOfClass:[NSString class]], @"asset url should be a nonnull string");
|
||||
NSAssert(type && [type isKindOfClass:[NSString class]], @"asset type should be a nonnull string");
|
||||
NSURL *url = [NSURL URLWithString:(NSString *)urlString];
|
||||
NSAssert(url, @"asset url should be a valid URL");
|
||||
|
||||
EXUpdatesAsset *asset = [[EXUpdatesAsset alloc] initWithKey:key type:(NSString *)type];
|
||||
asset.url = url;
|
||||
|
||||
if (metadata) {
|
||||
NSAssert([metadata isKindOfClass:[NSDictionary class]], @"asset metadata should be an object");
|
||||
asset.metadata = (NSDictionary *)metadata;
|
||||
}
|
||||
|
||||
if (mainBundleFilename) {
|
||||
NSAssert([mainBundleFilename isKindOfClass:[NSString class]], @"asset localPath should be a string");
|
||||
asset.mainBundleFilename = (NSString *)mainBundleFilename;
|
||||
}
|
||||
|
||||
[processedAssets addObject:asset];
|
||||
}
|
||||
|
||||
update.updateId = uuid;
|
||||
update.commitTime = [NSDate dateWithTimeIntervalSince1970:[(NSNumber *)commitTime doubleValue] / 1000];
|
||||
update.runtimeVersion = (NSString *)runtimeVersion;
|
||||
if (metadata) {
|
||||
update.metadata = (NSDictionary *)metadata;
|
||||
}
|
||||
update.status = EXUpdatesUpdateStatusPending;
|
||||
update.keep = YES;
|
||||
update.bundleUrl = bundleUrl;
|
||||
update.assets = processedAssets;
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
29
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate+Private.h
generated
vendored
Normal file
29
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate+Private.h
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesUpdate ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSUUID *updateId;
|
||||
@property (nonatomic, strong, readwrite) NSString *scopeKey;
|
||||
@property (nonatomic, strong, readwrite) NSDate *commitTime;
|
||||
@property (nonatomic, strong, readwrite) NSString *runtimeVersion;
|
||||
@property (nonatomic, strong, readwrite, nullable) NSDictionary *metadata;
|
||||
@property (nonatomic, assign, readwrite) BOOL keep;
|
||||
@property (nonatomic, strong, readwrite) NSURL *bundleUrl;
|
||||
@property (nonatomic, strong, readwrite) NSArray<EXUpdatesAsset *> *assets;
|
||||
@property (nonatomic, assign, readwrite) BOOL isDevelopmentMode;
|
||||
|
||||
@property (nonatomic, strong) EXUpdatesConfig *config;
|
||||
@property (nonatomic, strong, nullable) EXUpdatesDatabase *database;
|
||||
|
||||
- (instancetype)initWithRawManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(nullable EXUpdatesDatabase *)database;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
55
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate.h
generated
vendored
Normal file
55
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate.h
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesAsset.h>
|
||||
#import <EXUpdates/EXUpdatesConfig.h>
|
||||
|
||||
@class EXUpdatesDatabase;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, EXUpdatesUpdateStatus) {
|
||||
EXUpdatesUpdateStatusFailed = 0,
|
||||
EXUpdatesUpdateStatusReady = 1,
|
||||
EXUpdatesUpdateStatusLaunchable = 2,
|
||||
EXUpdatesUpdateStatusPending = 3,
|
||||
EXUpdatesUpdateStatusUnused = 4,
|
||||
EXUpdatesUpdateStatusEmbedded = 5,
|
||||
EXUpdatesUpdateStatusDevelopment = 6
|
||||
};
|
||||
|
||||
@interface EXUpdatesUpdate : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSUUID *updateId;
|
||||
@property (nonatomic, strong, readonly) NSString *scopeKey;
|
||||
@property (nonatomic, strong, readonly) NSDate *commitTime;
|
||||
@property (nonatomic, strong, readonly) NSString *runtimeVersion;
|
||||
@property (nonatomic, strong, readonly, nullable) NSDictionary * metadata;
|
||||
@property (nonatomic, assign, readonly) BOOL keep;
|
||||
@property (nonatomic, strong, readonly) NSArray<EXUpdatesAsset *> *assets;
|
||||
@property (nonatomic, assign, readonly) BOOL isDevelopmentMode;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSDictionary *rawManifest;
|
||||
|
||||
@property (nonatomic, assign) EXUpdatesUpdateStatus status;
|
||||
|
||||
+ (instancetype)updateWithId:(NSUUID *)updateId
|
||||
scopeKey:(NSString *)scopeKey
|
||||
commitTime:(NSDate *)commitTime
|
||||
runtimeVersion:(NSString *)runtimeVersion
|
||||
metadata:(nullable NSDictionary *)metadata
|
||||
status:(EXUpdatesUpdateStatus)status
|
||||
keep:(BOOL)keep
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database;
|
||||
|
||||
+ (instancetype)updateWithManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database;
|
||||
|
||||
+ (instancetype)updateWithEmbeddedManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(nullable EXUpdatesDatabase *)database;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
113
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate.m
generated
vendored
Normal file
113
node_modules/expo-updates/ios/EXUpdates/Update/EXUpdatesUpdate.m
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright © 2019 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXUpdates/EXUpdatesBareUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesDatabase.h>
|
||||
#import <EXUpdates/EXUpdatesLegacyUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesNewUpdate.h>
|
||||
#import <EXUpdates/EXUpdatesUpdate+Private.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXUpdatesUpdate ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSDictionary *rawManifest;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXUpdatesUpdate
|
||||
|
||||
- (instancetype)initWithRawManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(nullable EXUpdatesDatabase *)database
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_rawManifest = manifest;
|
||||
_config = config;
|
||||
_database = database;
|
||||
_scopeKey = config.scopeKey;
|
||||
_isDevelopmentMode = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)updateWithId:(NSUUID *)updateId
|
||||
scopeKey:(NSString *)scopeKey
|
||||
commitTime:(NSDate *)commitTime
|
||||
runtimeVersion:(NSString *)runtimeVersion
|
||||
metadata:(nullable NSDictionary *)metadata
|
||||
status:(EXUpdatesUpdateStatus)status
|
||||
keep:(BOOL)keep
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
{
|
||||
// for now, we store the entire managed manifest in the metadata field
|
||||
EXUpdatesUpdate *update = [[self alloc] initWithRawManifest:metadata ?: @{}
|
||||
config:config
|
||||
database:database];
|
||||
update.updateId = updateId;
|
||||
update.scopeKey = scopeKey;
|
||||
update.commitTime = commitTime;
|
||||
update.runtimeVersion = runtimeVersion;
|
||||
update.metadata = metadata;
|
||||
update.status = status;
|
||||
update.keep = keep;
|
||||
return update;
|
||||
}
|
||||
|
||||
+ (instancetype)updateWithManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(EXUpdatesDatabase *)database
|
||||
{
|
||||
if (config.usesLegacyManifest) {
|
||||
return [EXUpdatesLegacyUpdate updateWithLegacyManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
} else {
|
||||
return [EXUpdatesNewUpdate updateWithNewManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
}
|
||||
}
|
||||
|
||||
+ (instancetype)updateWithEmbeddedManifest:(NSDictionary *)manifest
|
||||
config:(EXUpdatesConfig *)config
|
||||
database:(nullable EXUpdatesDatabase *)database
|
||||
{
|
||||
if (config.usesLegacyManifest) {
|
||||
if (manifest[@"releaseId"]) {
|
||||
return [EXUpdatesLegacyUpdate updateWithLegacyManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
} else {
|
||||
return [EXUpdatesBareUpdate updateWithBareManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
}
|
||||
} else {
|
||||
if (manifest[@"runtimeVersion"]) {
|
||||
return [EXUpdatesNewUpdate updateWithNewManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
} else {
|
||||
return [EXUpdatesBareUpdate updateWithBareManifest:manifest
|
||||
config:config
|
||||
database:database];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<EXUpdatesAsset *> *)assets
|
||||
{
|
||||
if (!_assets && _database) {
|
||||
dispatch_sync(_database.databaseQueue, ^{
|
||||
NSError *error;
|
||||
self->_assets = [self->_database assetsWithUpdateId:self->_updateId error:&error];
|
||||
NSAssert(self->_assets, @"Assets should be nonnull when selected from DB: %@", error.localizedDescription);
|
||||
});
|
||||
}
|
||||
return _assets;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
Reference in New Issue
Block a user