xref: /expo/ios/Exponent/Kernel/Core/EXKernel.m (revision 489c2041)
10fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved.
20fbf6a33SBen Roth
30fbf6a33SBen Roth#import "EXAppState.h"
40187d3b3SBen Roth#import "EXAppViewController.h"
5e791dcdeSBen Roth#import "EXBuildConstants.h"
60fbf6a33SBen Roth#import "EXKernel.h"
7*7c3ed0c0SDouglas Lowder#import "EXAbstractLoader.h"
889c6c39cSEric Samelson#import "EXKernelAppRecord.h"
9b05926a2SBen Roth#import "EXKernelLinkingManager.h"
100fbf6a33SBen Roth#import "EXLinkingManager.h"
110fbf6a33SBen Roth#import "EXVersions.h"
1224a0cefbSTomasz Sapeta#import "EXHomeModule.h"
130fbf6a33SBen Roth
148810a243SEric Samelson#import <EXConstants/EXConstantsService.h>
150fbf6a33SBen Roth#import <React/RCTBridge+Private.h>
160fbf6a33SBen Roth#import <React/RCTEventDispatcher.h>
170fbf6a33SBen Roth#import <React/RCTModuleData.h>
180fbf6a33SBen Roth#import <React/RCTUtils.h>
190fbf6a33SBen Roth
2005aa4e6cSTomasz Sapeta#ifndef EX_DETACHED
2105aa4e6cSTomasz Sapeta// Kernel is DevMenu's delegate only in non-detached builds.
2205aa4e6cSTomasz Sapeta#import "EXDevMenuManager.h"
2305aa4e6cSTomasz Sapeta#import "EXDevMenuDelegateProtocol.h"
2405aa4e6cSTomasz Sapeta
2505aa4e6cSTomasz Sapeta@interface EXKernel () <EXDevMenuDelegateProtocol>
2605aa4e6cSTomasz Sapeta@end
2705aa4e6cSTomasz Sapeta#endif
2805aa4e6cSTomasz Sapeta
290fbf6a33SBen RothNS_ASSUME_NONNULL_BEGIN
300fbf6a33SBen Roth
310fbf6a33SBen RothNSString *kEXKernelErrorDomain = @"EXKernelErrorDomain";
320fbf6a33SBen RothNSString *kEXKernelShouldForegroundTaskEvent = @"foregroundTask";
330fbf6a33SBen RothNSString * const kEXKernelClearJSCacheUserDefaultsKey = @"EXKernelClearJSCacheUserDefaultsKey";
34d0248f72SBrent VatneNSString * const kEXReloadActiveAppRequest = @"EXReloadActiveAppRequest";
350fbf6a33SBen Roth
3689c6c39cSEric Samelson@interface EXKernel () <EXKernelAppRegistryDelegate>
370fbf6a33SBen Roth
380fbf6a33SBen Roth@end
390fbf6a33SBen Roth
4062cc3d03STomasz Sapeta// Protocol that should be implemented by all versions of EXAppState class.
4162cc3d03STomasz Sapeta@protocol EXAppStateProtocol
4262cc3d03STomasz Sapeta
4362cc3d03STomasz Sapeta@property (nonatomic, strong, readonly) NSString *lastKnownState;
4462cc3d03STomasz Sapeta
4562cc3d03STomasz Sapeta- (void)setState:(NSString *)state;
4662cc3d03STomasz Sapeta
4762cc3d03STomasz Sapeta@end
4862cc3d03STomasz Sapeta
490fbf6a33SBen Roth@implementation EXKernel
500fbf6a33SBen Roth
510fbf6a33SBen Roth+ (instancetype)sharedInstance
520fbf6a33SBen Roth{
530fbf6a33SBen Roth  static EXKernel *theKernel;
540fbf6a33SBen Roth  static dispatch_once_t once;
550fbf6a33SBen Roth  dispatch_once(&once, ^{
560fbf6a33SBen Roth    if (!theKernel) {
570fbf6a33SBen Roth      theKernel = [[EXKernel alloc] init];
580fbf6a33SBen Roth    }
590fbf6a33SBen Roth  });
600fbf6a33SBen Roth  return theKernel;
610fbf6a33SBen Roth}
620fbf6a33SBen Roth
630fbf6a33SBen Roth- (instancetype)init
640fbf6a33SBen Roth{
650fbf6a33SBen Roth  if (self = [super init]) {
6689c6c39cSEric Samelson    // init app registry: keep track of RN bridges we are running
6789c6c39cSEric Samelson    _appRegistry = [[EXKernelAppRegistry alloc] init];
6889c6c39cSEric Samelson    _appRegistry.delegate = self;
699f8d9b9dSBen Roth
709f8d9b9dSBen Roth    // init service registry: classes which manage shared resources among all bridges
71d7f51e5eSBen Roth    _serviceRegistry = [[EXKernelServiceRegistry alloc] init];
729f8d9b9dSBen Roth
7305aa4e6cSTomasz Sapeta#ifndef EX_DETACHED
74f67462bcSTomasz Sapeta    // Set the delegate of dev menu manager. Maybe it should be a separate class? Will see later once the delegate protocol gets too big.
75f67462bcSTomasz Sapeta    [[EXDevMenuManager sharedInstance] setDelegate:self];
7605aa4e6cSTomasz Sapeta#endif
77f67462bcSTomasz Sapeta
78d0248f72SBrent Vatne    // register for notifications to request reloading the visible app
79d0248f72SBrent Vatne    [[NSNotificationCenter defaultCenter] addObserver:self
80d0248f72SBrent Vatne                                             selector:@selector(_handleRequestReloadVisibleApp:)
81d0248f72SBrent Vatne                                                 name:kEXReloadActiveAppRequest
82d0248f72SBrent Vatne                                               object:nil];
83d0248f72SBrent Vatne
840fbf6a33SBen Roth    for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
850fbf6a33SBen Roth                             UIApplicationDidEnterBackgroundNotification,
860fbf6a33SBen Roth                             UIApplicationDidFinishLaunchingNotification,
870fbf6a33SBen Roth                             UIApplicationWillResignActiveNotification,
880fbf6a33SBen Roth                             UIApplicationWillEnterForegroundNotification]) {
890fbf6a33SBen Roth
900fbf6a33SBen Roth      [[NSNotificationCenter defaultCenter] addObserver:self
910fbf6a33SBen Roth                                               selector:@selector(_handleAppStateDidChange:)
920fbf6a33SBen Roth                                                   name:name
930fbf6a33SBen Roth                                                 object:nil];
940fbf6a33SBen Roth    }
952c229102SBen Roth    NSLog(@"Expo iOS Runtime Version %@", [EXBuildConstants sharedInstance].expoRuntimeVersion);
960fbf6a33SBen Roth  }
970fbf6a33SBen Roth  return self;
980fbf6a33SBen Roth}
990fbf6a33SBen Roth
1000fbf6a33SBen Roth- (void)dealloc
1010fbf6a33SBen Roth{
1020fbf6a33SBen Roth  [[NSNotificationCenter defaultCenter] removeObserver:self];
1030fbf6a33SBen Roth}
1040fbf6a33SBen Roth
1059f8d9b9dSBen Roth#pragma mark - bridge registry delegate
1069f8d9b9dSBen Roth
10789c6c39cSEric Samelson- (void)appRegistry:(EXKernelAppRegistry *)registry didRegisterAppRecord:(EXKernelAppRecord *)appRecord
1089f8d9b9dSBen Roth{
1099f8d9b9dSBen Roth  // forward to service registry
11089c6c39cSEric Samelson  [_serviceRegistry appRegistry:registry didRegisterAppRecord:appRecord];
1119f8d9b9dSBen Roth}
1129f8d9b9dSBen Roth
11389c6c39cSEric Samelson- (void)appRegistry:(EXKernelAppRegistry *)registry willUnregisterAppRecord:(EXKernelAppRecord *)appRecord
1149f8d9b9dSBen Roth{
1159f8d9b9dSBen Roth  // forward to service registry
11689c6c39cSEric Samelson  [_serviceRegistry appRegistry:registry willUnregisterAppRecord:appRecord];
1179f8d9b9dSBen Roth}
1189f8d9b9dSBen Roth
119f5e2fbc5SBen Roth#pragma mark - Interfacing with JS
120f5e2fbc5SBen Roth
121f5e2fbc5SBen Roth- (void)sendUrl:(NSString *)urlString toAppRecord:(EXKernelAppRecord *)app
122f5e2fbc5SBen Roth{
123f5e2fbc5SBen Roth  // fire a Linking url event on this (possibly versioned) bridge
124f5e2fbc5SBen Roth  EXReactAppManager *appManager = app.appManager;
125f5e2fbc5SBen Roth  id linkingModule = [self nativeModuleForAppManager:appManager named:@"LinkingManager"];
126f5e2fbc5SBen Roth  if (!linkingModule) {
127f5e2fbc5SBen Roth    DDLogError(@"Could not find the Linking module to open URL (%@)", urlString);
128f5e2fbc5SBen Roth  } else if ([linkingModule respondsToSelector:@selector(dispatchOpenUrlEvent:)]) {
129f5e2fbc5SBen Roth    [linkingModule dispatchOpenUrlEvent:[NSURL URLWithString:urlString]];
130f5e2fbc5SBen Roth  } else {
131f5e2fbc5SBen Roth    DDLogError(@"Linking module doesn't support the API we use to open URL (%@)", urlString);
132f5e2fbc5SBen Roth  }
133e7ac6e4fSBen Roth  [self _moveAppToVisible:app];
134f5e2fbc5SBen Roth}
1350fbf6a33SBen Roth
1365daa5350SBen Roth- (id)nativeModuleForAppManager:(EXReactAppManager *)appManager named:(NSString *)moduleName
1370fbf6a33SBen Roth{
1380fbf6a33SBen Roth  id destinationBridge = appManager.reactBridge;
1390fbf6a33SBen Roth
1400fbf6a33SBen Roth  if ([destinationBridge respondsToSelector:@selector(batchedBridge)]) {
1410fbf6a33SBen Roth    id batchedBridge = [destinationBridge batchedBridge];
1420fbf6a33SBen Roth    id moduleData = [batchedBridge moduleDataForName:moduleName];
1430fbf6a33SBen Roth
1440fbf6a33SBen Roth    if (moduleData) {
1450fbf6a33SBen Roth      return [moduleData instance];
1460fbf6a33SBen Roth    }
1470fbf6a33SBen Roth  } else {
148d2210950SBen Roth    // bridge can be null if the record is in an error state and never created a bridge.
149d2210950SBen Roth    if (destinationBridge) {
1500fbf6a33SBen Roth      DDLogError(@"Bridge does not support the API we use to get its underlying batched bridge");
1510fbf6a33SBen Roth    }
152d2210950SBen Roth  }
1530fbf6a33SBen Roth  return nil;
1540fbf6a33SBen Roth}
1550fbf6a33SBen Roth
1560fbf6a33SBen Roth/**
1570fbf6a33SBen Roth *  If the bridge has a batchedBridge or parentBridge selector, posts the notification on that object as well.
1580fbf6a33SBen Roth */
1590fbf6a33SBen Roth- (void)_postNotificationName: (NSNotificationName)name onAbstractBridge: (id)bridge
1600fbf6a33SBen Roth{
1610fbf6a33SBen Roth  [[NSNotificationCenter defaultCenter] postNotificationName:name object:bridge];
1620fbf6a33SBen Roth  if ([bridge respondsToSelector:@selector(batchedBridge)]) {
1630fbf6a33SBen Roth    [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge batchedBridge]];
1640fbf6a33SBen Roth  } else if ([bridge respondsToSelector:@selector(parentBridge)]) {
1650fbf6a33SBen Roth    [[NSNotificationCenter defaultCenter] postNotificationName:name object:[bridge parentBridge]];
1660fbf6a33SBen Roth  }
1670fbf6a33SBen Roth}
1680fbf6a33SBen Roth
1697e7b37d3SEric Samelson- (BOOL)_dispatchJSEvent:(NSString *)eventName body:(NSDictionary *)eventBody toApp:(EXKernelAppRecord *)appRecord
170f148175aSBen Roth{
1717e7b37d3SEric Samelson  if (!appRecord.appManager.reactBridge) {
1727e7b37d3SEric Samelson    return NO;
1737e7b37d3SEric Samelson  }
174c67a7dcdSBen Roth  [appRecord.appManager.reactBridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
175c67a7dcdSBen Roth                                             args:eventBody ? @[eventName, eventBody] : @[eventName]];
1767e7b37d3SEric Samelson  return YES;
1770fbf6a33SBen Roth}
1780fbf6a33SBen Roth
179d699b379SBen Roth#pragma mark - App props
180d699b379SBen Roth
1817100de8cSSzymon20000- (nullable NSDictionary *)initialAppPropsFromLaunchOptions:(NSDictionary *)launchOptions
182d699b379SBen Roth{
1837100de8cSSzymon20000  return nil;
184d699b379SBen Roth}
185d699b379SBen Roth
1868a7f6184SBen Roth#pragma mark - App State
1878e3c2ccdSBen Roth
18889fd6b24SBen Roth- (EXKernelAppRecord *)createNewAppWithUrl:(NSURL *)url initialProps:(nullable NSDictionary *)initialProps
189f5e2fbc5SBen Roth{
190c67a7dcdSBen Roth  NSString *recordId = [_appRegistry registerAppWithManifestUrl:url initialProps:initialProps];
191f5e2fbc5SBen Roth  EXKernelAppRecord *record = [_appRegistry recordForId:recordId];
192e7ac6e4fSBen Roth  [self _moveAppToVisible:record];
19389fd6b24SBen Roth  return record;
194f5e2fbc5SBen Roth}
195f5e2fbc5SBen Roth
196d0248f72SBrent Vatne
197b333b418SBrent Vatne- (void)reloadVisibleApp
198b333b418SBrent Vatne{
199b333b418SBrent Vatne  if (_browserController) {
200b333b418SBrent Vatne    [EXUtil performSynchronouslyOnMainThread:^{
201b333b418SBrent Vatne      [self->_browserController reloadVisibleApp];
202b333b418SBrent Vatne    }];
203b333b418SBrent Vatne  }
204b333b418SBrent Vatne}
205b333b418SBrent Vatne
2067f6367d0SBen Roth- (void)switchTasks
2070fbf6a33SBen Roth{
2087f6367d0SBen Roth  if (!_browserController) {
2097f6367d0SBen Roth    return;
2106f62abf2SBen Roth  }
2116f62abf2SBen Roth
2127f6367d0SBen Roth  if (_visibleApp != _appRegistry.homeAppRecord) {
21305aa4e6cSTomasz Sapeta#ifndef EX_DETACHED // Just to compile without access to EXDevMenuManager, we wouldn't get here either way because browser controller is unset in this case.
2147f6367d0SBen Roth    [EXUtil performSynchronouslyOnMainThread:^{
215f67462bcSTomasz Sapeta      [[EXDevMenuManager sharedInstance] toggle];
2167f6367d0SBen Roth    }];
21705aa4e6cSTomasz Sapeta#endif
218b05926a2SBen Roth  } else {
2197f6367d0SBen Roth    EXKernelAppRegistry *appRegistry = [EXKernel sharedInstance].appRegistry;
2207f6367d0SBen Roth    for (NSString *recordId in appRegistry.appEnumerator) {
2217f6367d0SBen Roth      EXKernelAppRecord *record = [appRegistry recordForId:recordId];
2227f6367d0SBen Roth      // foreground the first thing we find
223e7ac6e4fSBen Roth      [self _moveAppToVisible:record];
2247f6367d0SBen Roth    }
2257f6367d0SBen Roth  }
2267f6367d0SBen Roth}
2277f6367d0SBen Roth
228167fd314SWill Schurman- (void)reloadAppWithScopeKey:(NSString *)scopeKey
229928bc3baSBen Roth{
230167fd314SWill Schurman  EXKernelAppRecord *appRecord = [_appRegistry newestRecordWithScopeKey:scopeKey];
231928bc3baSBen Roth  if (_browserController) {
232928bc3baSBen Roth    [self createNewAppWithUrl:appRecord.appLoader.manifestUrl initialProps:nil];
233928bc3baSBen Roth  } else if (_appRegistry.standaloneAppRecord && appRecord == _appRegistry.standaloneAppRecord) {
234928bc3baSBen Roth    [appRecord.viewController refresh];
235928bc3baSBen Roth  }
236928bc3baSBen Roth}
237928bc3baSBen Roth
238167fd314SWill Schurman- (void)reloadAppFromCacheWithScopeKey:(NSString *)scopeKey
2399777fddbSEric Samelson{
240167fd314SWill Schurman  EXKernelAppRecord *appRecord = [_appRegistry newestRecordWithScopeKey:scopeKey];
2419777fddbSEric Samelson  [appRecord.viewController reloadFromCache];
2429777fddbSEric Samelson}
2439777fddbSEric Samelson
244e7ac6e4fSBen Roth- (void)viewController:(__unused EXViewController *)vc didNavigateAppToVisible:(EXKernelAppRecord *)appRecord
2457f6367d0SBen Roth{
2467f6367d0SBen Roth  EXKernelAppRecord *appRecordPreviouslyVisible = _visibleApp;
2477f6367d0SBen Roth  if (appRecord != appRecordPreviouslyVisible) {
2487f6367d0SBen Roth    if (appRecordPreviouslyVisible) {
249e7ac6e4fSBen Roth      [appRecordPreviouslyVisible.viewController appStateDidBecomeInactive];
2507f6367d0SBen Roth      [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:appRecordPreviouslyVisible.appManager.reactBridge];
25162cc3d03STomasz Sapeta      id<EXAppStateProtocol> appStateModule = [self nativeModuleForAppManager:appRecordPreviouslyVisible.appManager named:@"AppState"];
25262cc3d03STomasz Sapeta      if (appStateModule != nil) {
2530fbf6a33SBen Roth        [appStateModule setState:@"background"];
2540fbf6a33SBen Roth      }
2550fbf6a33SBen Roth    }
2567f6367d0SBen Roth    if (appRecord) {
257e7ac6e4fSBen Roth      [appRecord.viewController appStateDidBecomeActive];
2587f6367d0SBen Roth      [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:appRecord.appManager.reactBridge];
25962cc3d03STomasz Sapeta      id<EXAppStateProtocol> appStateModule = [self nativeModuleForAppManager:appRecord.appManager named:@"AppState"];
26062cc3d03STomasz Sapeta      if (appStateModule != nil) {
2610fbf6a33SBen Roth        [appStateModule setState:@"active"];
2620fbf6a33SBen Roth      }
2637f6367d0SBen Roth      _visibleApp = appRecord;
2640fbf6a33SBen Roth    } else {
2657f6367d0SBen Roth      _visibleApp = nil;
2667f6367d0SBen Roth    }
2677f6367d0SBen Roth
2687f6367d0SBen Roth    if (_visibleApp && _visibleApp != _appRegistry.homeAppRecord) {
2697f6367d0SBen Roth      [self _unregisterUnusedAppRecords];
2707f6367d0SBen Roth    }
2717f6367d0SBen Roth  }
2727f6367d0SBen Roth}
2737f6367d0SBen Roth
2747f6367d0SBen Roth- (void)_unregisterUnusedAppRecords
2757f6367d0SBen Roth{
2767f6367d0SBen Roth  for (NSString *recordId in _appRegistry.appEnumerator) {
2777f6367d0SBen Roth    EXKernelAppRecord *record = [_appRegistry recordForId:recordId];
2787f6367d0SBen Roth    if (record && record != _visibleApp) {
2797f6367d0SBen Roth      [_appRegistry unregisterAppWithRecordId:recordId];
2807f6367d0SBen Roth      break;
2810fbf6a33SBen Roth    }
2820fbf6a33SBen Roth  }
283b05926a2SBen Roth}
2840fbf6a33SBen Roth
2850fbf6a33SBen Roth- (void)_handleAppStateDidChange:(NSNotification *)notification
2860fbf6a33SBen Roth{
2870fbf6a33SBen Roth  NSString *newState;
2880fbf6a33SBen Roth
2890fbf6a33SBen Roth  if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) {
2900fbf6a33SBen Roth    newState = @"inactive";
2910fbf6a33SBen Roth  } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) {
2920fbf6a33SBen Roth    newState = @"background";
2930fbf6a33SBen Roth  } else {
2940fbf6a33SBen Roth    switch (RCTSharedApplication().applicationState) {
2950fbf6a33SBen Roth      case UIApplicationStateActive:
2960fbf6a33SBen Roth        newState = @"active";
2970fbf6a33SBen Roth        break;
2980fbf6a33SBen Roth      case UIApplicationStateBackground: {
2990fbf6a33SBen Roth        newState = @"background";
3000fbf6a33SBen Roth        break;
3010fbf6a33SBen Roth      }
3020fbf6a33SBen Roth      default: {
3030fbf6a33SBen Roth        newState = @"unknown";
3040fbf6a33SBen Roth        break;
3050fbf6a33SBen Roth      }
3060fbf6a33SBen Roth    }
3070fbf6a33SBen Roth  }
3080fbf6a33SBen Roth
3097f6367d0SBen Roth  if (_visibleApp) {
3107f6367d0SBen Roth    EXReactAppManager *appManager = _visibleApp.appManager;
31162cc3d03STomasz Sapeta    id<EXAppStateProtocol> appStateModule = [self nativeModuleForAppManager:appManager named:@"AppState"];
3120fbf6a33SBen Roth    NSString *lastKnownState;
31362cc3d03STomasz Sapeta    if (appStateModule != nil) {
3140fbf6a33SBen Roth      lastKnownState = [appStateModule lastKnownState];
3150fbf6a33SBen Roth      [appStateModule setState:newState];
3160fbf6a33SBen Roth    }
3170fbf6a33SBen Roth    if (!lastKnownState || ![newState isEqualToString:lastKnownState]) {
3180fbf6a33SBen Roth      if ([newState isEqualToString:@"active"]) {
319e7ac6e4fSBen Roth        [_visibleApp.viewController appStateDidBecomeActive];
3207f6367d0SBen Roth        [self _postNotificationName:kEXKernelBridgeDidForegroundNotification onAbstractBridge:appManager.reactBridge];
3210fbf6a33SBen Roth      } else if ([newState isEqualToString:@"background"]) {
322e7ac6e4fSBen Roth        [_visibleApp.viewController appStateDidBecomeInactive];
3237f6367d0SBen Roth        [self _postNotificationName:kEXKernelBridgeDidBackgroundNotification onAbstractBridge:appManager.reactBridge];
3240fbf6a33SBen Roth      }
3250fbf6a33SBen Roth    }
3260fbf6a33SBen Roth  }
3270fbf6a33SBen Roth}
3280fbf6a33SBen Roth
329d0248f72SBrent Vatne
330d0248f72SBrent Vatne- (void)_handleRequestReloadVisibleApp:(NSNotification *)notification
331d0248f72SBrent Vatne{
332d0248f72SBrent Vatne  [self reloadVisibleApp];
333d0248f72SBrent Vatne}
334d0248f72SBrent Vatne
335e7ac6e4fSBen Roth- (void)_moveAppToVisible:(EXKernelAppRecord *)appRecord
336e7ac6e4fSBen Roth{
337e7ac6e4fSBen Roth  if (_browserController) {
338e7ac6e4fSBen Roth    [EXUtil performSynchronouslyOnMainThread:^{
339d718b4b8SBen Roth      [self->_browserController moveAppToVisible:appRecord];
340e7ac6e4fSBen Roth    }];
341e7ac6e4fSBen Roth  }
342e7ac6e4fSBen Roth}
343e7ac6e4fSBen Roth
34405aa4e6cSTomasz Sapeta#ifndef EX_DETACHED
345f67462bcSTomasz Sapeta#pragma mark - EXDevMenuDelegateProtocol
346f67462bcSTomasz Sapeta
347f67462bcSTomasz Sapeta- (RCTBridge *)mainBridgeForDevMenuManager:(EXDevMenuManager *)manager
348f67462bcSTomasz Sapeta{
349f67462bcSTomasz Sapeta  return _appRegistry.homeAppRecord.appManager.reactBridge;
350f67462bcSTomasz Sapeta}
351f67462bcSTomasz Sapeta
352f67462bcSTomasz Sapeta- (nullable RCTBridge *)appBridgeForDevMenuManager:(EXDevMenuManager *)manager
353f67462bcSTomasz Sapeta{
354f67462bcSTomasz Sapeta  if (_visibleApp == _appRegistry.homeAppRecord) {
355f67462bcSTomasz Sapeta    return nil;
356f67462bcSTomasz Sapeta  }
357f67462bcSTomasz Sapeta  return _visibleApp.appManager.reactBridge;
358f67462bcSTomasz Sapeta}
359f67462bcSTomasz Sapeta
360f67462bcSTomasz Sapeta- (BOOL)devMenuManager:(EXDevMenuManager *)manager canChangeVisibility:(BOOL)visibility
361f67462bcSTomasz Sapeta{
362f67462bcSTomasz Sapeta  return !visibility || _visibleApp != _appRegistry.homeAppRecord;
363f67462bcSTomasz Sapeta}
364f67462bcSTomasz Sapeta
36505aa4e6cSTomasz Sapeta#endif
36605aa4e6cSTomasz Sapeta
3670fbf6a33SBen Roth@end
3680fbf6a33SBen Roth
3690fbf6a33SBen RothNS_ASSUME_NONNULL_END
370