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