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