From 57e4043c614dd958811d17faf1246b9ef2c8f7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 15 Dec 2021 11:57:45 +0100 Subject: [PATCH 1/4] Fix regex in AppDelegate & add fallback --- packages/app/plugin/src/ios/appDelegate.ts | 57 +++++++++++++++++----- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/app/plugin/src/ios/appDelegate.ts b/packages/app/plugin/src/ios/appDelegate.ts index 7059a21c5b..b6e69c773f 100644 --- a/packages/app/plugin/src/ios/appDelegate.ts +++ b/packages/app/plugin/src/ios/appDelegate.ts @@ -1,11 +1,16 @@ -import { ConfigPlugin, IOSConfig, withDangerousMod } from '@expo/config-plugins'; +import { ConfigPlugin, IOSConfig, WarningAggregator, withDangerousMod } from '@expo/config-plugins'; import { mergeContents } from '@expo/config-plugins/build/utils/generateCode'; import fs from 'fs'; const methodInvocationBlock = `[FIRApp configure];`; -// https://regex101.com/r/Imm3E8/1 +// https://regex101.com/r/mPgaq6/1 const methodInvocationLineMatcher = - /(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[\[RCTBridge alloc\])/g; + /(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[(\[RCTBridge alloc\]|self\.reactDelegate))(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[(\[RCTBridge alloc\]|self\.reactDelegate))/g; + +// https://regex101.com/r/nHrTa9/1/ +// if the above regex fails, we can use this one as a fallback: +const fallbackInvocationLineMatcher = + /-\s*\(BOOL\)\s*application:\s*\(UIApplication\s*\*\s*\)\s*\w+\s+didFinishLaunchingWithOptions:/g; export function modifyObjcAppDelegate(contents: string): string { // Add import @@ -22,15 +27,44 @@ export function modifyObjcAppDelegate(contents: string): string { return contents; } + if ( + !methodInvocationLineMatcher.test(contents) && + !fallbackInvocationLineMatcher.test(contents) + ) { + WarningAggregator.addWarningIOS( + '@react-native-firebase/app', + 'The AppDelegate.m may be malfolmed. Skiping adding Firebase to it.', + ); + return contents; + } + // Add invocation - return mergeContents({ - tag: '@react-native-firebase/app-didFinishLaunchingWithOptions', - src: contents, - newSrc: methodInvocationBlock, - anchor: methodInvocationLineMatcher, - offset: 0, // new line will be inserted right before matched anchor - comment: '//', - }).contents; + try { + return mergeContents({ + tag: '@react-native-firebase/app-didFinishLaunchingWithOptions', + src: contents, + newSrc: methodInvocationBlock, + anchor: methodInvocationLineMatcher, + offset: 0, // new line will be inserted right above matched anchor + comment: '//', + }).contents; + } catch (e: any) { + // tests if the opening `{` is in the new line + const multilineMatcher = new RegExp(fallbackInvocationLineMatcher.source + '.+\\n*{'); + const isHeaderMultiline = multilineMatcher.test(contents); + + // we fallback to another regex if the first one fails + return mergeContents({ + tag: '@react-native-firebase/app-didFinishLaunchingWithOptions-fallback', + src: contents, + newSrc: methodInvocationBlock, + anchor: fallbackInvocationLineMatcher, + // new line will be inserted right below matched anchor + // or two lines, if the `{` is in the new line + offset: isHeaderMultiline ? 2 : 1, + comment: '//', + }).contents; + } } export const withFirebaseAppDelegate: ConfigPlugin = config => { @@ -39,6 +73,7 @@ export const withFirebaseAppDelegate: ConfigPlugin = config => { async config => { const fileInfo = IOSConfig.Paths.getAppDelegate(config.modRequest.projectRoot); let contents = await fs.promises.readFile(fileInfo.path, 'utf-8'); + WarningAggregator.addWarningIOS('dupa', `${config.sdkVersion}`); if (fileInfo.language === 'objc') { contents = modifyObjcAppDelegate(contents); } else { From 22f2f98aa3d276e0573ea2094d2f6f72f7db59aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 15 Dec 2021 12:11:14 +0100 Subject: [PATCH 2/4] Update tests --- .../__snapshots__/iosPlugin.test.ts.snap | 145 +++++++++++++++++- .../fixtures/AppDelegate_bare_sdk43.m | 2 +- .../__tests__/fixtures/AppDelegate_fallback.m | 46 ++++++ .../__tests__/fixtures/AppDelegate_sdk44.m | 79 ++++++++++ .../app/plugin/__tests__/iosPlugin.test.ts | 21 ++- 5 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 packages/app/plugin/__tests__/fixtures/AppDelegate_fallback.m create mode 100644 packages/app/plugin/__tests__/fixtures/AppDelegate_sdk44.m diff --git a/packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap b/packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap index 5c5617fd9d..c43cf7f5a4 100644 --- a/packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap +++ b/packages/app/plugin/__tests__/__snapshots__/iosPlugin.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m (SDK 43+) 1`] = ` -"// This AppDelegate template is used in Expo SDK 43 and newer +exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m (SDK 43) 1`] = ` +"// This AppDelegate template is used in Expo SDK 43 // It is (nearly) identical to the pure template used when // creating a bare React Native app (without Expo) @@ -94,6 +94,147 @@ static void InitializeFlipper(UIApplication *application) { " `; +exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m with Expo ReactDelegate support (SDK 44+) 1`] = ` +"// This AppDelegate prebuild template is used in Expo SDK 44+ +// It has the RCTBridge to be created by Expo ReactDelegate + +#import \\"AppDelegate.h\\" +@import Firebase; + +#import +#import +#import +#import +#import + +#if defined(FB_SONARKIT_ENABLED) && __has_include() +#import +#import +#import +#import +#import +#import + +static void InitializeFlipper(UIApplication *application) { + FlipperClient *client = [FlipperClient sharedClient]; + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; + [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; + [client addPlugin:[FlipperKitReactPlugin new]]; + [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [client start]; +} +#endif + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ +#if defined(FB_SONARKIT_ENABLED) && __has_include() + InitializeFlipper(application); +#endif + +// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda +[FIRApp configure]; +// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions + RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; + RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@\\"main\\" initialProperties:nil]; + rootView.backgroundColor = [UIColor whiteColor]; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [self.reactDelegate createRootViewController]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; + } + +- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge +{ + // If you'd like to export some custom RCTBridgeModules, add them here! + return @[]; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { + #ifdef DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\\"index\\" fallbackResource:nil]; + #else + return [[NSBundle mainBundle] URLForResource:@\\"main\\" withExtension:@\\"jsbundle\\"]; + #endif +} + +// Linking API +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + return [RCTLinkingManager application:application openURL:url options:options]; +} + +// Universal Links +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +} + +@end +" +`; + +exports[`Config Plugin iOS Tests tests changes made to AppDelegate.m with fallback regex (if the original one fails) 1`] = ` +"// This AppDelegate template is modified to have RCTBridge +// created in some non-standard way or not created at all. +// This should trigger the fallback regex in iOS AppDelegate Expo plugin. + +// some parts omitted to be short + +#import \\"AppDelegate.h\\" +@import Firebase; + +#import +#import +#import +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ +// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions-fallback - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda +[FIRApp configure]; +// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions-fallback + +// The generated code should appear above ^^^ +#if defined(FB_SONARKIT_ENABLED) && __has_include() + InitializeFlipper(application); +#endif + + // the line below is malfolmed not to be matched by the Expo plugin regex + // RCTBridge* briddge = [RCTBridge new]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:briddge moduleName:@\\"main\\" initialProperties:nil]; + id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@\\"RCTRootViewBackgroundColor\\"]; + if (rootViewBackgroundColor != nil) { + rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor]; + } else { + rootView.backgroundColor = [UIColor whiteColor]; + } + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; + } + +@end +" +`; + exports[`Config Plugin iOS Tests tests changes made to old AppDelegate.m (SDK 42) 1`] = ` "// This AppDelegate prebuild template is used in Expo SDK 42 and older // It expects the old react-native-unimodules architecture (UM* prefix) diff --git a/packages/app/plugin/__tests__/fixtures/AppDelegate_bare_sdk43.m b/packages/app/plugin/__tests__/fixtures/AppDelegate_bare_sdk43.m index 2c628dfa65..74eb997d9d 100644 --- a/packages/app/plugin/__tests__/fixtures/AppDelegate_bare_sdk43.m +++ b/packages/app/plugin/__tests__/fixtures/AppDelegate_bare_sdk43.m @@ -1,4 +1,4 @@ -// This AppDelegate template is used in Expo SDK 43 and newer +// This AppDelegate template is used in Expo SDK 43 // It is (nearly) identical to the pure template used when // creating a bare React Native app (without Expo) diff --git a/packages/app/plugin/__tests__/fixtures/AppDelegate_fallback.m b/packages/app/plugin/__tests__/fixtures/AppDelegate_fallback.m new file mode 100644 index 0000000000..360359a7c8 --- /dev/null +++ b/packages/app/plugin/__tests__/fixtures/AppDelegate_fallback.m @@ -0,0 +1,46 @@ +// This AppDelegate template is modified to have RCTBridge +// created in some non-standard way or not created at all. +// This should trigger the fallback regex in iOS AppDelegate Expo plugin. + +// some parts omitted to be short + +#import "AppDelegate.h" + +#import +#import +#import +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + +// The generated code should appear above ^^^ +#if defined(FB_SONARKIT_ENABLED) && __has_include() + InitializeFlipper(application); +#endif + + // the line below is malfolmed not to be matched by the Expo plugin regex + // RCTBridge* briddge = [RCTBridge new]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:briddge moduleName:@"main" initialProperties:nil]; + id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"]; + if (rootViewBackgroundColor != nil) { + rootView.backgroundColor = [RCTConvert UIColor:rootViewBackgroundColor]; + } else { + rootView.backgroundColor = [UIColor whiteColor]; + } + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; + } + +@end diff --git a/packages/app/plugin/__tests__/fixtures/AppDelegate_sdk44.m b/packages/app/plugin/__tests__/fixtures/AppDelegate_sdk44.m new file mode 100644 index 0000000000..7c4864d7d8 --- /dev/null +++ b/packages/app/plugin/__tests__/fixtures/AppDelegate_sdk44.m @@ -0,0 +1,79 @@ +// This AppDelegate prebuild template is used in Expo SDK 44+ +// It has the RCTBridge to be created by Expo ReactDelegate + +#import "AppDelegate.h" + +#import +#import +#import +#import +#import + +#if defined(FB_SONARKIT_ENABLED) && __has_include() +#import +#import +#import +#import +#import +#import + +static void InitializeFlipper(UIApplication *application) { + FlipperClient *client = [FlipperClient sharedClient]; + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; + [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; + [client addPlugin:[FlipperKitReactPlugin new]]; + [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [client start]; +} +#endif + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ +#if defined(FB_SONARKIT_ENABLED) && __has_include() + InitializeFlipper(application); +#endif + + RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; + RCTRootView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:nil]; + rootView.backgroundColor = [UIColor whiteColor]; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [self.reactDelegate createRootViewController]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + + [super application:application didFinishLaunchingWithOptions:launchOptions]; + + return YES; + } + +- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge +{ + // If you'd like to export some custom RCTBridgeModules, add them here! + return @[]; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { + #ifdef DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; + #else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + #endif +} + +// Linking API +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + return [RCTLinkingManager application:application openURL:url options:options]; +} + +// Universal Links +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +} + +@end diff --git a/packages/app/plugin/__tests__/iosPlugin.test.ts b/packages/app/plugin/__tests__/iosPlugin.test.ts index 2ccc41639b..05ed9b76cd 100644 --- a/packages/app/plugin/__tests__/iosPlugin.test.ts +++ b/packages/app/plugin/__tests__/iosPlugin.test.ts @@ -12,7 +12,7 @@ describe('Config Plugin iOS Tests', function () { expect(result).toMatchSnapshot(); }); - it('tests changes made to AppDelegate.m (SDK 43+)', async function () { + it('tests changes made to AppDelegate.m (SDK 43)', async function () { const appDelegate = await fs.readFile( path.join(__dirname, './fixtures/AppDelegate_bare_sdk43.m'), { @@ -22,4 +22,23 @@ describe('Config Plugin iOS Tests', function () { const result = modifyObjcAppDelegate(appDelegate); expect(result).toMatchSnapshot(); }); + + it('tests changes made to AppDelegate.m with Expo ReactDelegate support (SDK 44+)', async function () { + const appDelegate = await fs.readFile(path.join(__dirname, './fixtures/AppDelegate_sdk44.m'), { + encoding: 'utf8', + }); + const result = modifyObjcAppDelegate(appDelegate); + expect(result).toMatchSnapshot(); + }); + + it('tests changes made to AppDelegate.m with fallback regex (if the original one fails)', async function () { + const appDelegate = await fs.readFile( + path.join(__dirname, './fixtures/AppDelegate_fallback.m'), + { + encoding: 'utf8', + }, + ); + const result = modifyObjcAppDelegate(appDelegate); + expect(result).toMatchSnapshot(); + }); }); From 9ff9c7038902b760e9da4155f021dd2c52a62165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 15 Dec 2021 12:49:02 +0100 Subject: [PATCH 3/4] Minor regex fix --- packages/app/plugin/src/ios/appDelegate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/app/plugin/src/ios/appDelegate.ts b/packages/app/plugin/src/ios/appDelegate.ts index b6e69c773f..21b743e66b 100644 --- a/packages/app/plugin/src/ios/appDelegate.ts +++ b/packages/app/plugin/src/ios/appDelegate.ts @@ -5,7 +5,7 @@ import fs from 'fs'; const methodInvocationBlock = `[FIRApp configure];`; // https://regex101.com/r/mPgaq6/1 const methodInvocationLineMatcher = - /(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[(\[RCTBridge alloc\]|self\.reactDelegate))(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[(\[RCTBridge alloc\]|self\.reactDelegate))/g; + /(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[(\[RCTBridge alloc\]|self\.reactDelegate))/g; // https://regex101.com/r/nHrTa9/1/ // if the above regex fails, we can use this one as a fallback: @@ -73,7 +73,6 @@ export const withFirebaseAppDelegate: ConfigPlugin = config => { async config => { const fileInfo = IOSConfig.Paths.getAppDelegate(config.modRequest.projectRoot); let contents = await fs.promises.readFile(fileInfo.path, 'utf-8'); - WarningAggregator.addWarningIOS('dupa', `${config.sdkVersion}`); if (fileInfo.language === 'objc') { contents = modifyObjcAppDelegate(contents); } else { From 8e3a90b3d0b17804ff02d8c014388cdd51c91787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Klocek?= Date: Wed, 15 Dec 2021 15:30:06 +0100 Subject: [PATCH 4/4] Apply suggestion Co-authored-by: Mike Hardy --- packages/app/plugin/src/ios/appDelegate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/plugin/src/ios/appDelegate.ts b/packages/app/plugin/src/ios/appDelegate.ts index 21b743e66b..06063eb3d4 100644 --- a/packages/app/plugin/src/ios/appDelegate.ts +++ b/packages/app/plugin/src/ios/appDelegate.ts @@ -33,7 +33,7 @@ export function modifyObjcAppDelegate(contents: string): string { ) { WarningAggregator.addWarningIOS( '@react-native-firebase/app', - 'The AppDelegate.m may be malfolmed. Skiping adding Firebase to it.', + 'Unable to determine correct Firebase insertion point in AppDelegate.m. Skipping Firebase addition.', ); return contents; }