1const { withDangerousMod, IOSConfig } = require('@expo/config-plugins'); 2const fs = require('fs-extra'); 3 4const withDevMenu = config => { 5 return withDangerousMod(config, [ 6 'ios', 7 async config => { 8 const fileInfo = IOSConfig.Paths.getAppDelegate(config.modRequest.projectRoot); 9 let contents = await fs.readFile(fileInfo.path, 'utf-8'); 10 if (fileInfo.language === 'objc') { 11 // Add DevMenu imports 12 if (!contents.includes('#import <React/RCTDevMenu.h>')) { 13 contents = contents.replace( 14 /#import "AppDelegate.h"/g, 15 `#import "AppDelegate.h" 16#import <React/RCTDevMenu.h>` 17 ); 18 } 19 if (!contents.includes('#import <React/RCTUtils.h>')) { 20 contents = contents.replace( 21 /#import "AppDelegate.h"/g, 22 `#import "AppDelegate.h" 23#import <React/RCTUtils.h>` 24 ); 25 } 26 27 // Make the extraModules mutable 28 const modulesRegex = /NSArray<id<RCTBridgeModule>>\s?\*extraModules\s?=\s?\[_moduleRegistryAdapter extraModulesForBridge:bridge\];/; 29 if (contents.match(modulesRegex)) { 30 contents = contents.replace( 31 modulesRegex, 32 'NSMutableArray<id<RCTBridgeModule>> *extraModules = [NSMutableArray arrayWithArray:[_moduleRegistryAdapter extraModulesForBridge:bridge]];' 33 ); 34 } 35 36 // Add DevMenu back 37 if ( 38 !contents.includes( 39 '[extraModules addObject:(id<RCTBridgeModule>)[[RCTDevMenu alloc] init]];' 40 ) 41 ) { 42 contents = contents.replace( 43 /return extraModules;/g, 44 `[extraModules addObject:(id<RCTBridgeModule>)[[RCTDevMenu alloc] init]]; 45 return extraModules;` 46 ); 47 } 48 49 // Add swizzling invocation 50 if (!contents.includes(swizzleMethodInvocationBlock)) { 51 // self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] 52 contents = contents.replace( 53 /self\.moduleRegistryAdapter = \[\[UMModuleRegistryAdapter alloc\]/g, 54 `${swizzleMethodInvocationBlock} 55 self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]` 56 ); 57 } 58 59 // Add swizzling method 60 if (!contents.match(/\(void\)\s?ensureReactMethodSwizzlingSetUp/g)) { 61 const sections = contents.split('@end'); 62 sections[sections.length - 2] += swizzleMethodBlock; 63 contents = sections.join('@end'); 64 } 65 } else { 66 throw new Error( 67 `Cannot append DevMenu module to AppDelegate of language "${fileInfo.language}"` 68 ); 69 } 70 await fs.writeFile(fileInfo.path, contents); 71 72 return config; 73 }, 74 ]); 75}; 76 77const swizzleMethodInvocationBlock = `[self ensureReactMethodSwizzlingSetUp];`; 78 79const swizzleMethodBlock = ` 80// Bring back React method swizzling removed from its Pod 81// when integrating with Expo client. 82// https://github.com/expo/react-native/commit/7f2912e8005ea6e81c45935241081153b822b988 83- (void)ensureReactMethodSwizzlingSetUp 84{ 85 static dispatch_once_t onceToken; 86 dispatch_once(&onceToken, ^{ 87 #pragma clang diagnostic push 88 #pragma clang diagnostic ignored "-Wundeclared-selector" 89 // RCTKeyCommands.m 90 // swizzle UIResponder 91 RCTSwapInstanceMethods([UIResponder class], 92 @selector(keyCommands), 93 @selector(RCT_keyCommands)); 94 95 // RCTDevMenu.m 96 // We're swizzling here because it's poor form to override methods in a category, 97 // however UIWindow doesn't actually implement motionEnded:withEvent:, so there's 98 // no need to call the original implementation. 99 RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:)); 100 #pragma clang diagnostic pop 101 }); 102} 103`; 104 105module.exports = withDevMenu; 106