Skip to content

Commit e4966ef

Browse files
mgray88mikehardy
authored andcommitted
feat(auth, expo): add support for AppDelegate.swift
1 parent 339834d commit e4966ef

File tree

5 files changed

+208
-36
lines changed

5 files changed

+208
-36
lines changed

packages/auth/plugin/__tests__/__snapshots__/iosPlugin_openUrlFix.test.ts.snap

+52
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,58 @@ exports[`Config Plugin iOS Tests - openUrlFix munges AppDelegate correctly - App
324324
"
325325
`;
326326
327+
exports[`Config Plugin iOS Tests - openUrlFix munges AppDelegate correctly - AppDelegate_sdk53.swift 1`] = `
328+
"import React
329+
import Expo
330+
331+
@UIApplicationMain
332+
public class AppDelegate: ExpoAppDelegate {
333+
public override func application(
334+
_ application: UIApplication,
335+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
336+
) -> Bool {
337+
self.moduleName = "HelloWorld"
338+
self.initialProps = [:]
339+
340+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
341+
}
342+
343+
public override func bundleURL() -> URL? {
344+
#if DEBUG
345+
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
346+
#else
347+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
348+
#endif
349+
}
350+
351+
// Linking API
352+
public override func application(
353+
_ app: UIApplication,
354+
open url: URL,
355+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
356+
) -> Bool {
357+
// @generated begin @react-native-firebase/auth-openURL - expo prebuild (DO NOT MODIFY)
358+
if url.host.toLowerCase() == "firebaseauth" {
359+
// invocations for Firebase Auth are handled elsewhere and should not be forwarded to Expo Router
360+
return false
361+
}
362+
// @generated end @react-native-firebase/auth-openURL
363+
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
364+
}
365+
366+
// Universal Links
367+
public override func application(
368+
_ application: UIApplication,
369+
continue userActivity: NSUserActivity,
370+
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
371+
) -> Bool {
372+
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
373+
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
374+
}
375+
}
376+
"
377+
`;
378+
327379
exports[`Config Plugin iOS Tests - openUrlFix must match positiveTemplateCases[0] 1`] = `
328380
"- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
329381
// @generated begin @react-native-firebase/auth-openURL - expo prebuild (DO NOT MODIFY) sync-5e029a87ac71df3ca5665387eb712d1b32274c6a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React
2+
import Expo
3+
4+
@UIApplicationMain
5+
public class AppDelegate: ExpoAppDelegate {
6+
public override func application(
7+
_ application: UIApplication,
8+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
9+
) -> Bool {
10+
self.moduleName = "HelloWorld"
11+
self.initialProps = [:]
12+
13+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
14+
}
15+
16+
public override func bundleURL() -> URL? {
17+
#if DEBUG
18+
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
19+
#else
20+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
21+
#endif
22+
}
23+
24+
// Universal Links
25+
public override func application(
26+
_ application: UIApplication,
27+
continue userActivity: NSUserActivity,
28+
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
29+
) -> Bool {
30+
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
31+
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React
2+
import Expo
3+
4+
@UIApplicationMain
5+
public class AppDelegate: ExpoAppDelegate {
6+
public override func application(
7+
_ application: UIApplication,
8+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
9+
) -> Bool {
10+
self.moduleName = "HelloWorld"
11+
self.initialProps = [:]
12+
13+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
14+
}
15+
16+
public override func bundleURL() -> URL? {
17+
#if DEBUG
18+
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
19+
#else
20+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
21+
#endif
22+
}
23+
24+
// Linking API
25+
public override func application(
26+
_ app: UIApplication,
27+
open url: URL,
28+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
29+
) -> Bool {
30+
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
31+
}
32+
33+
// Universal Links
34+
public override func application(
35+
_ application: UIApplication,
36+
continue userActivity: NSUserActivity,
37+
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
38+
) -> Bool {
39+
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
40+
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
41+
}
42+
}

packages/auth/plugin/__tests__/iosPlugin_openUrlFix.test.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ensureFirebaseSwizzlingIsEnabled,
1111
appDelegateOpenUrlInsertionPointAfter,
1212
multiline_appDelegateOpenUrlInsertionPointAfter,
13+
modifyAppDelegate,
1314
} from '../src/ios/openUrlFix';
1415
import type { ExpoConfigPluginEntry } from '../src/ios/openUrlFix';
1516

@@ -188,15 +189,16 @@ describe('Config Plugin iOS Tests - openUrlFix', () => {
188189
});
189190

190191
const appDelegateFixturesPatch = [
191-
'AppDelegate_bare_sdk43.m',
192-
'AppDelegate_sdk44.m',
193-
'AppDelegate_sdk45.mm',
192+
{ fixtureName: 'AppDelegate_bare_sdk43.m', language: 'objc' },
193+
{ fixtureName: 'AppDelegate_sdk44.m', language: 'objc' },
194+
{ fixtureName: 'AppDelegate_sdk45.mm', language: 'objcpp' },
195+
{ fixtureName: 'AppDelegate_sdk53.swift', language: 'swift' },
194196
];
195-
appDelegateFixturesPatch.forEach(fixtureName => {
197+
appDelegateFixturesPatch.forEach(({ fixtureName, language }) => {
196198
it(`munges AppDelegate correctly - ${fixtureName}`, async () => {
197199
const fixturePath = path.join(__dirname, 'fixtures', fixtureName);
198200
const appDelegate = await fs.readFile(fixturePath, { encoding: 'utf-8' });
199-
const result = modifyObjcAppDelegate(appDelegate);
201+
const result = modifyAppDelegate(appDelegate, language);
200202
expect(result).toMatchSnapshot();
201203
});
202204

@@ -209,7 +211,7 @@ describe('Config Plugin iOS Tests - openUrlFix', () => {
209211
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
210212
modResults: {
211213
path: fixturePath,
212-
language: 'objc',
214+
language: language,
213215
contents: appDelegate,
214216
} as AppDelegateProjectFile,
215217
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
@@ -226,8 +228,12 @@ describe('Config Plugin iOS Tests - openUrlFix', () => {
226228
});
227229
});
228230

229-
const appDelegateFixturesNoop = ['AppDelegate_sdk42.m', 'AppDelegate_fallback.m'];
230-
appDelegateFixturesNoop.forEach(fixtureName => {
231+
const appDelegateFixturesNoop = [
232+
{ fixtureName: 'AppDelegate_sdk42.m', language: 'objc' },
233+
{ fixtureName: 'AppDelegate_fallback.m', language: 'objc' },
234+
{ fixtureName: 'AppDelegate_noOpenURL_sdk53.swift', language: 'swift' },
235+
];
236+
appDelegateFixturesNoop.forEach(({ fixtureName, language }) => {
231237
it(`skips AppDelegate without openURL - ${fixtureName}`, async () => {
232238
const fixturePath = path.join(__dirname, 'fixtures', fixtureName);
233239
const appDelegate = await fs.readFile(fixturePath, { encoding: 'utf-8' });
@@ -238,7 +244,7 @@ describe('Config Plugin iOS Tests - openUrlFix', () => {
238244
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
239245
modResults: {
240246
path: fixturePath,
241-
language: 'objc',
247+
language: language,
242248
contents: appDelegate,
243249
} as AppDelegateProjectFile,
244250
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
@@ -264,7 +270,7 @@ describe('Config Plugin iOS Tests - openUrlFix', () => {
264270
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
265271
modResults: {
266272
path: fixturePath,
267-
language: 'objc',
273+
language: language,
268274
contents: appDelegate,
269275
} as AppDelegateProjectFile,
270276
modRawConfig: { name: 'TestName', slug: 'TestSlug' },

packages/auth/plugin/src/ios/openUrlFix.ts

+65-26
Original file line numberDiff line numberDiff line change
@@ -62,35 +62,40 @@ export function withOpenUrlFixForAppDelegate({
6262
const { language, contents } = config.modResults;
6363
const configValue = props?.ios?.captchaOpenUrlFix || 'default';
6464

65-
if (['objc', 'objcpp'].includes(language)) {
66-
const newContents = modifyObjcAppDelegate(contents);
67-
if (newContents === null) {
68-
if (configValue === true) {
69-
throw new Error("Failed to apply iOS openURL fix because no 'openURL' method was found");
70-
} else {
71-
WarningAggregator.addWarningIOS(
72-
'@react-native-firebase/auth',
73-
"Skipping iOS openURL fix because no 'openURL' method was found",
74-
);
75-
return config;
76-
}
65+
const newContents = modifyAppDelegate(contents, language);
66+
if (newContents === null) {
67+
if (configValue === true) {
68+
throw new Error("Failed to apply iOS openURL fix because no 'openURL' method was found");
7769
} else {
78-
if (configValue === 'default') {
79-
WarningAggregator.addWarningIOS(
80-
'@react-native-firebase/auth',
81-
'modifying iOS AppDelegate openURL method to ignore firebaseauth reCAPTCHA redirect URLs',
82-
);
83-
}
84-
return {
85-
...config,
86-
modResults: {
87-
...config.modResults,
88-
contents: newContents,
89-
},
90-
};
70+
WarningAggregator.addWarningIOS(
71+
'@react-native-firebase/auth',
72+
"Skipping iOS openURL fix because no 'openURL' method was found",
73+
);
74+
return config;
75+
}
76+
} else {
77+
if (configValue === 'default') {
78+
WarningAggregator.addWarningIOS(
79+
'@react-native-firebase/auth',
80+
'modifying iOS AppDelegate openURL method to ignore firebaseauth reCAPTCHA redirect URLs',
81+
);
9182
}
83+
return {
84+
...config,
85+
modResults: {
86+
...config.modResults,
87+
contents: newContents,
88+
},
89+
};
90+
}
91+
}
92+
93+
export function modifyAppDelegate(contents: string, language: string): string | null {
94+
if (language === 'objc' || language === 'objcpp') {
95+
return modifyObjcAppDelegate(contents);
96+
} else if (language === 'swift') {
97+
return modifySwiftAppDelegate(contents);
9298
} else {
93-
// TODO: Support Swift
9499
throw new Error(`Don't know how to apply openUrlFix to AppDelegate of language "${language}"`);
95100
}
96101
}
@@ -147,6 +152,40 @@ export function modifyObjcAppDelegate(contents: string): string | null {
147152
}).contents;
148153
}
149154

155+
// NOTE: `mergeContents()` doesn't support newlines for the `anchor` regex, so we have to replace it manually
156+
const skipOpenUrlForFirebaseAuthBlockSwift: string = `\
157+
// @generated begin @react-native-firebase/auth-openURL - expo prebuild (DO NOT MODIFY)
158+
if url.host.toLowerCase() == "firebaseauth" {
159+
// invocations for Firebase Auth are handled elsewhere and should not be forwarded to Expo Router
160+
return false
161+
}
162+
// @generated end @react-native-firebase/auth-openURL\
163+
`;
164+
165+
export const appDelegateSwiftOpenUrlInsertionPointAfter: RegExp =
166+
/public\s*override\s*func\s*application\s*\(\n\s*_\s*app\s*:\s*UIApplication,\n\s*open\s*url\s*:\s*URL,\n\s*options\s*:\s*\[UIApplication\.OpenURLOptionsKey\s*:\s*Any\]\s*=\s*\[:\]\n\s*\)\s*->\s*Bool\s*{\n/;
167+
168+
export function modifySwiftAppDelegate(contents: string): string | null {
169+
const pattern = appDelegateSwiftOpenUrlInsertionPointAfter;
170+
const fullMatch = contents.match(pattern);
171+
if (!fullMatch) {
172+
if (contents.match(/open url\s*:/)) {
173+
throw new Error(
174+
[
175+
"Failed to apply 'captchaOpenUrlFix' but detected 'openURL' method.",
176+
"Please manually apply the fix to your AppDelegate's openURL method,",
177+
"then update your app.config.json by configuring the '@react-native-firebase/auth' plugin",
178+
'to set `captchaOpenUrlFix: false`.',
179+
].join(' '),
180+
);
181+
} else {
182+
// openURL method was not found in AppDelegate
183+
return null;
184+
}
185+
}
186+
return contents.replace(pattern, `${fullMatch[0]}${skipOpenUrlForFirebaseAuthBlockSwift}\n`);
187+
}
188+
150189
export type ExpoConfigPluginEntry = string | [] | [string] | [string, any];
151190

152191
// Search the ExpoConfig plugins array to see if `pluginName` is present

0 commit comments

Comments
 (0)