17c10e2d9SEvan Baconconst { withDangerousMod, IOSConfig } = require('@expo/config-plugins'); 27c10e2d9SEvan Baconconst fs = require('fs-extra'); 37c10e2d9SEvan Bacon 4*35f78160SBartosz Kaszubowskiconst withDevMenu = (config) => { 57c10e2d9SEvan Bacon return withDangerousMod(config, [ 67c10e2d9SEvan Bacon 'ios', 7*35f78160SBartosz Kaszubowski async (config) => { 87c10e2d9SEvan Bacon const fileInfo = IOSConfig.Paths.getAppDelegate(config.modRequest.projectRoot); 97c10e2d9SEvan Bacon let contents = await fs.readFile(fileInfo.path, 'utf-8'); 107c10e2d9SEvan Bacon if (fileInfo.language === 'objc') { 117c10e2d9SEvan Bacon // Add DevMenu imports 127c10e2d9SEvan Bacon if (!contents.includes('#import <React/RCTDevMenu.h>')) { 137c10e2d9SEvan Bacon contents = contents.replace( 147c10e2d9SEvan Bacon /#import "AppDelegate.h"/g, 157c10e2d9SEvan Bacon `#import "AppDelegate.h" 167c10e2d9SEvan Bacon#import <React/RCTDevMenu.h>` 177c10e2d9SEvan Bacon ); 187c10e2d9SEvan Bacon } 197c10e2d9SEvan Bacon if (!contents.includes('#import <React/RCTUtils.h>')) { 207c10e2d9SEvan Bacon contents = contents.replace( 217c10e2d9SEvan Bacon /#import "AppDelegate.h"/g, 227c10e2d9SEvan Bacon `#import "AppDelegate.h" 237c10e2d9SEvan Bacon#import <React/RCTUtils.h>` 247c10e2d9SEvan Bacon ); 257c10e2d9SEvan Bacon } 267c10e2d9SEvan Bacon 277c10e2d9SEvan Bacon // Make the extraModules mutable 286da15324SBartosz Kaszubowski const modulesRegex = 296da15324SBartosz Kaszubowski /NSArray<id<RCTBridgeModule>>\s?\*extraModules\s?=\s?\[_moduleRegistryAdapter extraModulesForBridge:bridge\];/; 307c10e2d9SEvan Bacon if (contents.match(modulesRegex)) { 317c10e2d9SEvan Bacon contents = contents.replace( 327c10e2d9SEvan Bacon modulesRegex, 337c10e2d9SEvan Bacon 'NSMutableArray<id<RCTBridgeModule>> *extraModules = [NSMutableArray arrayWithArray:[_moduleRegistryAdapter extraModulesForBridge:bridge]];' 347c10e2d9SEvan Bacon ); 357c10e2d9SEvan Bacon } 367c10e2d9SEvan Bacon 377c10e2d9SEvan Bacon // Add DevMenu back 387c10e2d9SEvan Bacon if ( 397c10e2d9SEvan Bacon !contents.includes( 407c10e2d9SEvan Bacon '[extraModules addObject:(id<RCTBridgeModule>)[[RCTDevMenu alloc] init]];' 417c10e2d9SEvan Bacon ) 427c10e2d9SEvan Bacon ) { 437c10e2d9SEvan Bacon contents = contents.replace( 447c10e2d9SEvan Bacon /return extraModules;/g, 457c10e2d9SEvan Bacon `[extraModules addObject:(id<RCTBridgeModule>)[[RCTDevMenu alloc] init]]; 467c10e2d9SEvan Bacon return extraModules;` 477c10e2d9SEvan Bacon ); 487c10e2d9SEvan Bacon } 497c10e2d9SEvan Bacon 507c10e2d9SEvan Bacon // Add swizzling invocation 517c10e2d9SEvan Bacon if (!contents.includes(swizzleMethodInvocationBlock)) { 527c10e2d9SEvan Bacon // self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] 537c10e2d9SEvan Bacon contents = contents.replace( 547c10e2d9SEvan Bacon /self\.moduleRegistryAdapter = \[\[UMModuleRegistryAdapter alloc\]/g, 557c10e2d9SEvan Bacon `${swizzleMethodInvocationBlock} 567c10e2d9SEvan Bacon self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc]` 577c10e2d9SEvan Bacon ); 587c10e2d9SEvan Bacon } 597c10e2d9SEvan Bacon 607c10e2d9SEvan Bacon // Add swizzling method 617c10e2d9SEvan Bacon if (!contents.match(/\(void\)\s?ensureReactMethodSwizzlingSetUp/g)) { 627c10e2d9SEvan Bacon const sections = contents.split('@end'); 637c10e2d9SEvan Bacon sections[sections.length - 2] += swizzleMethodBlock; 647c10e2d9SEvan Bacon contents = sections.join('@end'); 657c10e2d9SEvan Bacon } 667c10e2d9SEvan Bacon } else { 677c10e2d9SEvan Bacon throw new Error( 687c10e2d9SEvan Bacon `Cannot append DevMenu module to AppDelegate of language "${fileInfo.language}"` 697c10e2d9SEvan Bacon ); 707c10e2d9SEvan Bacon } 717c10e2d9SEvan Bacon await fs.writeFile(fileInfo.path, contents); 727c10e2d9SEvan Bacon 737c10e2d9SEvan Bacon return config; 747c10e2d9SEvan Bacon }, 757c10e2d9SEvan Bacon ]); 767c10e2d9SEvan Bacon}; 777c10e2d9SEvan Bacon 787c10e2d9SEvan Baconconst swizzleMethodInvocationBlock = `[self ensureReactMethodSwizzlingSetUp];`; 797c10e2d9SEvan Bacon 807c10e2d9SEvan Baconconst swizzleMethodBlock = ` 817c10e2d9SEvan Bacon// Bring back React method swizzling removed from its Pod 827c10e2d9SEvan Bacon// when integrating with Expo client. 837c10e2d9SEvan Bacon// https://github.com/expo/react-native/commit/7f2912e8005ea6e81c45935241081153b822b988 847c10e2d9SEvan Bacon- (void)ensureReactMethodSwizzlingSetUp 857c10e2d9SEvan Bacon{ 867c10e2d9SEvan Bacon static dispatch_once_t onceToken; 877c10e2d9SEvan Bacon dispatch_once(&onceToken, ^{ 887c10e2d9SEvan Bacon #pragma clang diagnostic push 897c10e2d9SEvan Bacon #pragma clang diagnostic ignored "-Wundeclared-selector" 907c10e2d9SEvan Bacon // RCTKeyCommands.m 917c10e2d9SEvan Bacon // swizzle UIResponder 927c10e2d9SEvan Bacon RCTSwapInstanceMethods([UIResponder class], 937c10e2d9SEvan Bacon @selector(keyCommands), 947c10e2d9SEvan Bacon @selector(RCT_keyCommands)); 957c10e2d9SEvan Bacon 967c10e2d9SEvan Bacon // RCTDevMenu.m 977c10e2d9SEvan Bacon // We're swizzling here because it's poor form to override methods in a category, 987c10e2d9SEvan Bacon // however UIWindow doesn't actually implement motionEnded:withEvent:, so there's 997c10e2d9SEvan Bacon // no need to call the original implementation. 1007c10e2d9SEvan Bacon RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:)); 1017c10e2d9SEvan Bacon #pragma clang diagnostic pop 1027c10e2d9SEvan Bacon }); 1037c10e2d9SEvan Bacon} 1047c10e2d9SEvan Bacon`; 1057c10e2d9SEvan Bacon 1067c10e2d9SEvan Baconmodule.exports = withDevMenu; 107