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 "EXUpdates.h" 10#import "EXUpdatesDatabaseManager.h" 11#import "EXUpdatesManager.h" 12 13#import <EXUpdates/EXUpdatesFileDownloader.h> 14#import <EXUpdates/EXUpdatesRemoteAppLoader.h> 15 16#import <React/RCTBridge.h> 17#import <React/RCTUtils.h> 18 19NSString * const EXUpdatesEventNameNew = @"Expo.nativeUpdatesEvent"; 20NSString * const EXUpdatesUpdateAvailableEventType = @"updateAvailable"; 21 22@interface EXUpdatesManager () 23 24@property (nonatomic, strong) EXAppLoader *manifestAppLoader; 25 26@end 27 28@implementation EXUpdatesManager 29 30- (void)notifyApp:(EXKernelAppRecord *)appRecord 31ofDownloadWithManifest:(NSDictionary * _Nullable)manifest 32 isNew:(BOOL)isBundleNew 33 error:(NSError * _Nullable)error; 34{ 35 NSDictionary *body; 36 NSDictionary *bodyLegacy; 37 if (error) { 38 body = @{ 39 @"type": EXUpdatesErrorEventType, 40 @"message": error.localizedDescription 41 }; 42 } else if (isBundleNew) { 43 if (!manifest) { 44 // prevent a crash, but this shouldn't ever happen 45 manifest = @{}; 46 } 47 bodyLegacy = @{ 48 @"type": EXUpdatesDownloadFinishedEventType, 49 @"manifest": manifest 50 }; 51 body = @{ 52 @"type": EXUpdatesUpdateAvailableEventType, 53 @"manifest": manifest 54 }; 55 } else { 56 body = @{ 57 @"type": EXUpdatesNotAvailableEventType 58 }; 59 } 60 RCTBridge *bridge = appRecord.appManager.reactBridge; 61 if (appRecord.status == kEXKernelAppRecordStatusRunning) { 62 // for SDK 38 and below 63 if ([self _doesBridgeSupportUpdatesModule:bridge]) { 64 [bridge.scopedModules.updates sendEventWithBody:bodyLegacy ?: body]; 65 } 66 // for SDK 39+ 67 [bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[EXUpdatesEventNameNew, body]]; 68 } 69} 70 71# pragma mark - internal 72 73- (EXAppLoader *)_appLoaderWithScopedModule:(id)scopedModule 74{ 75 NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId; 76 return [self _appLoaderWithExperienceId:experienceId]; 77} 78 79- (EXAppLoader *)_appLoaderWithExperienceId:(NSString *)experienceId 80{ 81 EXKernelAppRecord *appRecord = [[EXKernel sharedInstance].appRegistry newestRecordWithExperienceId:experienceId]; 82 return appRecord.appLoader; 83} 84 85# pragma mark - EXUpdatesBindingDelegate 86 87- (EXUpdatesConfig *)configForExperienceId:(NSString *)experienceId 88{ 89 return [self _appLoaderWithExperienceId:experienceId].config; 90} 91 92- (id<EXUpdatesSelectionPolicy>)selectionPolicyForExperienceId:(NSString *)experienceId 93{ 94 return [self _appLoaderWithExperienceId:experienceId].selectionPolicy; 95} 96 97- (nullable EXUpdatesUpdate *)launchedUpdateForExperienceId:(NSString *)experienceId 98{ 99 return [self _appLoaderWithExperienceId:experienceId].appLauncher.launchedUpdate; 100} 101 102- (nullable NSDictionary *)assetFilesMapForExperienceId:(NSString *)experienceId 103{ 104 return [self _appLoaderWithExperienceId:experienceId].appLauncher.assetFilesMap; 105} 106 107- (BOOL)isUsingEmbeddedAssetsForExperienceId:(NSString *)experienceId 108{ 109 return NO; 110} 111 112- (BOOL)isStartedForExperienceId:(NSString *)experienceId 113{ 114 return [self _appLoaderWithExperienceId:experienceId].appLauncher != nil; 115} 116 117- (BOOL)isEmergencyLaunchForExperienceId:(NSString *)experienceId 118{ 119 return NO; 120} 121 122- (void)requestRelaunchForExperienceId:(NSString *)experienceId withCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion 123{ 124 [[EXKernel sharedInstance] reloadAppFromCacheWithExperienceId:experienceId]; 125 completion(YES); 126} 127 128# pragma mark - EXUpdatesScopedModuleDelegate 129 130- (void)updatesModuleDidSelectReload:(id)scopedModule 131{ 132 NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId; 133 [[EXKernel sharedInstance] reloadAppWithExperienceId:experienceId]; 134} 135 136- (void)updatesModuleDidSelectReloadFromCache:(id)scopedModule 137{ 138 NSString *experienceId = ((EXScopedBridgeModule *)scopedModule).experienceId; 139 [[EXKernel sharedInstance] reloadAppFromCacheWithExperienceId:experienceId]; 140} 141 142- (void)updatesModule:(id)scopedModule 143didRequestManifestWithCacheBehavior:(EXManifestCacheBehavior)cacheBehavior 144 success:(void (^)(NSDictionary * _Nonnull))success 145 failure:(void (^)(NSError * _Nonnull))failure 146{ 147 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 148 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 149 return; 150 } 151 152 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 153 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 154 155 EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:appLoader.config]; 156 [fileDownloader downloadManifestFromURL:appLoader.config.updateUrl 157 withDatabase:databaseKernelService.database 158 cacheDirectory:databaseKernelService.updatesDirectory 159 successBlock:^(EXUpdatesUpdate *update) { 160 success(update.rawManifest); 161 } errorBlock:^(NSError *error, NSURLResponse *response) { 162 failure(error); 163 }]; 164} 165 166- (void)updatesModule:(nonnull id)scopedModule didRequestBundleWithManifest:(nonnull NSDictionary *)manifest progress:(nonnull void (^)(NSDictionary * _Nonnull))progress success:(nonnull void (^)(NSData * _Nonnull))success failure:(nonnull void (^)(NSError * _Nonnull))failure { 167 // TODO: remove this stub 168} 169 170 171- (void)updatesModule:(id)scopedModule 172didRequestBundleWithCompletionQueue:(dispatch_queue_t)completionQueue 173 start:(void (^)(void))startBlock 174 success:(void (^)(NSDictionary * _Nullable))success 175 failure:(void (^)(NSError * _Nonnull))failure 176{ 177 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 178 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 179 return; 180 } 181 182 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 183 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 184 185 EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:appLoader.config database:databaseKernelService.database directory:databaseKernelService.updatesDirectory completionQueue:completionQueue]; 186 [remoteAppLoader loadUpdateFromUrl:appLoader.config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) { 187 BOOL shouldLoad = [appLoader.selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:appLoader.appLauncher.launchedUpdate]; 188 if (shouldLoad) { 189 startBlock(); 190 } 191 return shouldLoad; 192 } success:^(EXUpdatesUpdate * _Nullable update) { 193 if (update) { 194 success(update.rawManifest); 195 } else { 196 success(nil); 197 } 198 } error:^(NSError * _Nonnull error) { 199 failure(error); 200 }]; 201} 202 203- (BOOL)_doesBridgeSupportUpdatesModule:(RCTBridge *)bridge 204{ 205 // sdk versions prior to 26 didn't include this module. 206 return ([bridge.scopedModules respondsToSelector:@selector(updates)]); 207} 208 209@end 210