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