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:(EXUpdatesRawManifest * _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 // prevent a crash, but this shouldn't ever happen 52 NSDictionary *rawManifestJSON = manifest ? manifest.rawManifestJSON : @{}; 53 bodyLegacy = @{ 54 @"type": EXUpdatesDownloadFinishedEventType, 55 @"manifest": rawManifestJSON 56 }; 57 body = @{ 58 @"type": EXUpdatesUpdateAvailableEventType, 59 @"manifest": rawManifestJSON 60 }; 61 } else { 62 body = @{ 63 @"type": EXUpdatesNotAvailableEventType 64 }; 65 } 66 RCTBridge *bridge = appRecord.appManager.reactBridge; 67 if (appRecord.status == kEXKernelAppRecordStatusRunning) { 68 // for SDK 38 and below 69 [bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[EXUpdatesEventNameLegacy, bodyLegacy ?: body]]; 70 // for SDK 39+ 71 [bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[EXUpdatesEventName, body]]; 72 } 73} 74 75# pragma mark - internal 76 77- (EXAppLoader *)_appLoaderWithScopedModule:(id)scopedModule 78{ 79 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 80 return [self _appLoaderWithScopeKey:scopeKey]; 81} 82 83- (EXAppLoader *)_appLoaderWithScopeKey:(NSString *)scopeKey 84{ 85 EXKernelAppRecord *appRecord = [[EXKernel sharedInstance].appRegistry newestRecordWithScopeKey:scopeKey]; 86 return appRecord.appLoader; 87} 88 89# pragma mark - EXUpdatesBindingDelegate 90 91- (EXUpdatesConfig *)configForScopeKey:(NSString *)scopeKey 92{ 93 return [self _appLoaderWithScopeKey:scopeKey].config; 94} 95 96- (EXUpdatesSelectionPolicy *)selectionPolicyForScopeKey:(NSString *)scopeKey 97{ 98 return [self _appLoaderWithScopeKey:scopeKey].selectionPolicy; 99} 100 101- (nullable EXUpdatesUpdate *)launchedUpdateForScopeKey:(NSString *)scopeKey 102{ 103 return [self _appLoaderWithScopeKey:scopeKey].appLauncher.launchedUpdate; 104} 105 106- (nullable NSDictionary *)assetFilesMapForScopeKey:(NSString *)scopeKey 107{ 108 return [self _appLoaderWithScopeKey:scopeKey].appLauncher.assetFilesMap; 109} 110 111- (BOOL)isUsingEmbeddedAssetsForScopeKey:(NSString *)scopeKey 112{ 113 return NO; 114} 115 116- (BOOL)isStartedForScopeKey:(NSString *)scopeKey 117{ 118 return [self _appLoaderWithScopeKey:scopeKey].appLauncher != nil; 119} 120 121- (BOOL)isEmergencyLaunchForScopeKey:(NSString *)scopeKey 122{ 123 return [self _appLoaderWithScopeKey:scopeKey].isEmergencyLaunch; 124} 125 126- (void)requestRelaunchForScopeKey:(NSString *)scopeKey withCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion 127{ 128 [[EXKernel sharedInstance] reloadAppFromCacheWithScopeKey:scopeKey]; 129 completion(YES); 130} 131 132# pragma mark - EXUpdatesScopedModuleDelegate 133 134- (void)updatesModuleDidSelectReload:(id)scopedModule 135{ 136 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 137 [[EXKernel sharedInstance] reloadAppWithScopeKey:scopeKey]; 138} 139 140- (void)updatesModuleDidSelectReloadFromCache:(id)scopedModule 141{ 142 NSString *scopeKey = ((EXScopedBridgeModule *)scopedModule).scopeKey; 143 [[EXKernel sharedInstance] reloadAppFromCacheWithScopeKey:scopeKey]; 144} 145 146- (void)updatesModule:(id)scopedModule 147didRequestManifestWithCacheBehavior:(EXManifestCacheBehavior)cacheBehavior 148 success:(void (^)(EXUpdatesRawManifest * _Nonnull))success 149 failure:(void (^)(NSError * _Nonnull))failure 150{ 151 if ([EXEnvironment sharedEnvironment].isDetached && ![EXEnvironment sharedEnvironment].areRemoteUpdatesEnabled) { 152 failure(RCTErrorWithMessage(@"Remote updates are disabled in app.json")); 153 return; 154 } 155 156 EXUpdatesDatabaseManager *databaseKernelService = [EXKernel sharedInstance].serviceRegistry.updatesDatabaseManager; 157 EXAppLoader *appLoader = [self _appLoaderWithScopedModule:scopedModule]; 158 159 EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:appLoader.config]; 160 [fileDownloader downloadManifestFromURL:appLoader.config.updateUrl 161 withDatabase:databaseKernelService.database 162 extraHeaders:nil 163 successBlock:^(EXUpdatesUpdate *update) { 164 success(update.rawManifest); 165 } errorBlock:^(NSError *error, NSURLResponse *response) { 166 failure(error); 167 }]; 168} 169 170 171- (void)updatesModule:(id)scopedModule 172didRequestBundleWithCompletionQueue:(dispatch_queue_t)completionQueue 173 start:(void (^)(void))startBlock 174 success:(void (^)(EXUpdatesRawManifest * _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 filters:update.manifestFilters]; 188 if (shouldLoad) { 189 startBlock(); 190 } 191 return shouldLoad; 192 } asset:^(EXUpdatesAsset *asset, NSUInteger successfulAssetCount, NSUInteger failedAssetCount, NSUInteger totalAssetCount) { 193 // do nothing for now 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