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