1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXAppLoader+Updates.h" 4#import "EXEnvironment.h" 5#import "EXKernel.h" 6#import "EXKernelAppRecord.h" 7#import "EXReactAppManager.h" 8#import "EXScopedModuleRegistry.h" 9#import "EXUpdatesDatabaseManager.h" 10#import "EXUpdatesManager.h" 11 12#import <EXUpdates/EXUpdatesFileDownloader.h> 13#import <EXUpdates/EXUpdatesRemoteAppLoader.h> 14 15#import <React/RCTBridge.h> 16#import <React/RCTUtils.h> 17 18NSString * const EXUpdatesEventName = @"Expo.nativeUpdatesEvent"; 19NSString * const EXUpdatesErrorEventType = @"error"; 20NSString * const EXUpdatesUpdateAvailableEventType = @"updateAvailable"; 21NSString * const EXUpdatesNotAvailableEventType = @"noUpdateAvailable"; 22 23@interface EXUpdatesManager () 24 25@property (nonatomic, strong) EXAppLoader *manifestAppLoader; 26 27@end 28 29@implementation EXUpdatesManager 30 31- (void)notifyApp:(EXKernelAppRecord *)appRecord 32ofDownloadWithManifest:(EXManifestsManifest * _Nullable)manifest 33 isNew:(BOOL)isBundleNew 34 error:(NSError * _Nullable)error; 35{ 36 NSDictionary *body; 37 if (error) { 38 body = @{ 39 @"type": EXUpdatesErrorEventType, 40 @"message": error.localizedDescription 41 }; 42 } else if (isBundleNew) { 43 // prevent a crash, but this shouldn't ever happen 44 NSDictionary *rawManifestJSON = manifest ? manifest.rawManifestJSON : @{}; 45 body = @{ 46 @"type": EXUpdatesUpdateAvailableEventType, 47 @"manifest": rawManifestJSON 48 }; 49 } else { 50 body = @{ 51 @"type": EXUpdatesNotAvailableEventType 52 }; 53 } 54 RCTBridge *bridge = appRecord.appManager.reactBridge; 55 if (appRecord.status == kEXKernelAppRecordStatusRunning) { 56 [bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[EXUpdatesEventName, body]]; 57 } 58} 59 60# pragma mark - internal 61 62- (EXAppLoader *)_appLoaderWithScopedModule:(id)scopedModule 63{ 64 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 65 return [self _appLoaderWithScopeKey:scopeKey]; 66} 67 68- (EXAppLoader *)_appLoaderWithScopeKey:(NSString *)scopeKey 69{ 70 EXKernelAppRecord *appRecord = [[EXKernel sharedInstance].appRegistry newestRecordWithScopeKey:scopeKey]; 71 return appRecord.appLoader; 72} 73 74# pragma mark - EXUpdatesBindingDelegate 75 76- (EXUpdatesConfig *)configForScopeKey:(NSString *)scopeKey 77{ 78 return [self _appLoaderWithScopeKey:scopeKey].config; 79} 80 81- (EXUpdatesSelectionPolicy *)selectionPolicyForScopeKey:(NSString *)scopeKey 82{ 83 return [self _appLoaderWithScopeKey:scopeKey].selectionPolicy; 84} 85 86- (nullable EXUpdatesUpdate *)launchedUpdateForScopeKey:(NSString *)scopeKey 87{ 88 return [self _appLoaderWithScopeKey:scopeKey].appLauncher.launchedUpdate; 89} 90 91- (nullable NSDictionary *)assetFilesMapForScopeKey:(NSString *)scopeKey 92{ 93 return [self _appLoaderWithScopeKey:scopeKey].appLauncher.assetFilesMap; 94} 95 96- (BOOL)isUsingEmbeddedAssetsForScopeKey:(NSString *)scopeKey 97{ 98 return NO; 99} 100 101- (BOOL)isStartedForScopeKey:(NSString *)scopeKey 102{ 103 return [self _appLoaderWithScopeKey:scopeKey].appLauncher != nil; 104} 105 106- (BOOL)isEmergencyLaunchForScopeKey:(NSString *)scopeKey 107{ 108 return [self _appLoaderWithScopeKey:scopeKey].isEmergencyLaunch; 109} 110 111- (void)requestRelaunchForScopeKey:(NSString *)scopeKey withCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion 112{ 113 [[EXKernel sharedInstance] reloadAppFromCacheWithScopeKey:scopeKey]; 114 completion(YES); 115} 116 117# pragma mark - EXUpdatesScopedModuleDelegate 118 119- (void)updatesModuleDidSelectReload:(id)scopedModule 120{ 121 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 122 [[EXKernel sharedInstance] reloadAppWithScopeKey:scopeKey]; 123} 124 125- (void)updatesModuleDidSelectReloadFromCache:(id)scopedModule 126{ 127 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 128 [[EXKernel sharedInstance] reloadAppFromCacheWithScopeKey:scopeKey]; 129} 130 131- (void)updatesModule:(id)scopedModule 132didRequestManifestWithCacheBehavior:(EXManifestCacheBehavior)cacheBehavior 133 success:(void (^)(EXManifestsManifest * _Nonnull))success 134 failure:(void (^)(NSError * _Nonnull))failure 135{ 136 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 137 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 138 return; 139 } 140 141 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 142 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 143 144 EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:appLoader.config]; 145 [fileDownloader downloadManifestFromURL:appLoader.config.updateUrl 146 withDatabase:databaseKernelService.database 147 extraHeaders:nil 148 successBlock:^(EXUpdatesUpdate *update) { 149 success(update.manifest); 150 } errorBlock:^(NSError *error) { 151 failure(error); 152 }]; 153} 154 155 156- (void)updatesModule:(id)scopedModule 157didRequestBundleWithCompletionQueue:(dispatch_queue_t)completionQueue 158 start:(void (^)(void))startBlock 159 success:(void (^)(EXManifestsManifest * _Nullable))success 160 failure:(void (^)(NSError * _Nonnull))failure 161{ 162 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 163 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 164 return; 165 } 166 167 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 168 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 169 170 EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:appLoader.config database:databaseKernelService.database directory:databaseKernelService.updatesDirectory launchedUpdate:nil completionQueue:completionQueue]; 171 [remoteAppLoader loadUpdateFromUrl:appLoader.config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) { 172 BOOL shouldLoad = [appLoader.selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:appLoader.appLauncher.launchedUpdate filters:update.manifestFilters]; 173 if (shouldLoad) { 174 startBlock(); 175 } 176 return shouldLoad; 177 } asset:^(EXUpdatesAsset *asset, NSUInteger successfulAssetCount, NSUInteger failedAssetCount, NSUInteger totalAssetCount) { 178 // do nothing for now 179 } success:^(EXUpdatesUpdate * _Nullable update) { 180 if (update) { 181 success(update.manifest); 182 } else { 183 success(nil); 184 } 185 } error:^(NSError * _Nonnull error) { 186 failure(error); 187 }]; 188} 189 190@end 191