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 successBlock:^(EXUpdatesUpdate *update) { 165 success(update.rawManifest); 166 } errorBlock:^(NSError *error, NSURLResponse *response) { 167 failure(error); 168 }]; 169} 170 171 172- (void)updatesModule:(id)scopedModule 173didRequestBundleWithCompletionQueue:(dispatch_queue_t)completionQueue 174 start:(void (^)(void))startBlock 175 success:(void (^)(NSDictionary * _Nullable))success 176 failure:(void (^)(NSError * _Nonnull))failure 177{ 178 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 179 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 180 return; 181 } 182 183 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 184 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 185 186 EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:appLoader.config database:databaseKernelService.database directory:databaseKernelService.updatesDirectory completionQueue:completionQueue]; 187 [remoteAppLoader loadUpdateFromUrl:appLoader.config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) { 188 BOOL shouldLoad = [appLoader.selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:appLoader.appLauncher.launchedUpdate filters:nil]; 189 if (shouldLoad) { 190 startBlock(); 191 } 192 return shouldLoad; 193 } success:^(EXUpdatesUpdate * _Nullable update) { 194 if (update) { 195 success(update.rawManifest); 196 } else { 197 success(nil); 198 } 199 } error:^(NSError * _Nonnull error) { 200 failure(error); 201 }]; 202} 203 204@end 205