Skip to content

fix(auth): restore app extension build broken by UIApplication.shared - #16584

Merged
ncooke3 merged 2 commits into
firebase:mainfrom
jeffwall-curlewlabs:fix/auth-notification-manager-app-extension
Sep 2, 2026
Merged

ncooke3 merged 2 commits into
firebase:mainfrom
jeffwall-curlewlabs:fix/auth-notification-manager-app-extension

Conversation

@jeffwall-curlewlabs

Copy link
Copy Markdown
Contributor

Fixes #16583.

The problem

#16449 added an AuthNotificationApplication protocol so AuthNotificationManager could be unit tested with a fake. UIApplicationDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:) takes a concrete UIApplication, so the call site gained a downcast with a fallback:

let appObj = (self.application as? UIApplication) ?? UIApplication.shared

UIApplication.shared is NS_EXTENSION_UNAVAILABLE_IOS, so since 12.18.0 FirebaseAuth does not compile for targets built with -application-extension:

AuthNotificationManager.swift:133:80: error: 'shared' is unavailable in application extensions for iOS

The fix

Vend the UIApplication the delegate callback needs through the protocol. The production conformance is UIApplication itself, so it returns self; the two test fakes return UIApplication.shared from the test target, which is not built for app extensions. That takes UIApplication.shared out of the SDK module entirely rather than hiding it behind a runtime lookup.

Behaviour is unchanged. In production self.application is always a real UIApplicationAuthNotificationManager is constructed only in Auth.protectedDataInitialization(), after its GULAppEnvironmentUtil.isAppExtension() early return — and the fake delegates in the tests ignore the argument.

Alternative considered

The narrower change would be the reflection idiom already used at Auth.swift:1704, AuthDefaultUIDelegate.swift:34 and TOTPSecret.swift:64, which would keep the diff inside one file. I went with the protocol because those three sites genuinely need the shared application at runtime in an app, whereas this one never does. Happy to switch if you would prefer the smaller change.

Verification

xcodebuild build -scheme FirebaseAuth -destination 'generic/platform=iOS' \
  APPLICATION_EXTENSION_API_ONLY=YES CODE_SIGNING_ALLOWED=NO
tree result
main (168647e) error: 'shared' is unavailable in application extensions for iOS
this branch ** BUILD SUCCEEDED **
12.17.0 tag (control) ** BUILD SUCCEEDED **

AuthUnit passes on iOS Simulator, Xcode 26.6. SwiftFormat 0.55.5, the Mintfile pin, reports no formatting changes needed.

Notes, not part of this change

@google-cla

google-cla Bot commented Sep 1, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@paulb777

paulb777 commented Sep 1, 2026

Copy link
Copy Markdown
Member

@jeffwall-curlewlabs Thanks for the PR. Please sign the CLA and we'll take a look

firebase#16449 introduced an AuthNotificationApplication protocol so
AuthNotificationManager could be unit tested with a fake. Because
UIApplicationDelegate.application(_:didReceiveRemoteNotification:
fetchCompletionHandler:) takes a concrete UIApplication, the call site
gained a downcast with a `?? UIApplication.shared` fallback.

UIApplication.shared is NS_EXTENSION_UNAVAILABLE_IOS, so since 12.18.0
FirebaseAuth no longer compiles for targets built with
-application-extension.

Vend the UIApplication the delegate callback needs through the protocol
instead. The production conformance is UIApplication itself and returns
self; the test fakes return UIApplication.shared from the test target,
which is not built for app extensions. That removes UIApplication.shared
from the module entirely rather than hiding it behind a runtime lookup.

Behaviour is unchanged: in production self.application is always a real
UIApplication, because AuthNotificationManager is only constructed in
Auth.protectedDataInitialization() after its isAppExtension() early
return, and the fake delegates in the tests ignore the argument.

Fixes firebase#16583.
@jeffwall-curlewlabs
jeffwall-curlewlabs force-pushed the fix/auth-notification-manager-app-extension branch from c8936dc to dffec26 Compare September 1, 2026 19:09
@paulb777 paulb777 added this to the 12.19.0 - M186 milestone Sep 1, 2026
@itamarbareket

itamarbareket commented Sep 2, 2026

Copy link
Copy Markdown

Tried this on a real project — it fixes the reported error, but the app-extension build still fails further along.

Wanted to flag this before it lands, in case the goal is "app-extension builds work again" rather than specifically "this one line compiles."

Setup: CocoaPods 1.16.2, Xcode 26.6, use_frameworks! :linkage => :static, FirebaseAuth 12.18.0 via react-native-firebase 26.3.3. Three targets — an app, a WidgetKit extension, and an App Clip. The app and the widget share one pod target through an abstract_target, so CocoaPods sets APPLICATION_EXTENSION_API_ONLY = YES on the FirebaseAuth pod.

What I did: applied this PR's three edits to the vendored pod source (protocol member, UIApplication conformance, call site), kept APPLICATION_EXTENSION_API_ONLY = YES, wiped DerivedData, clean build.

Result: AuthNotificationManager.swift:133 compiles — that part works. The build then fails in the generated FirebaseAuth-Swift.h when an Objective-C consumer imports it:

FirebaseAuth-Swift.h:1140:41  no type or protocol named 'FIRComponentLifecycleMaintainer'
FirebaseAuth-Swift.h:1545:41  no type or protocol named 'SFSafariViewControllerDelegate'
FirebaseAuth-Swift.h:1555:54  cannot find protocol declaration for 'WKNavigationDelegate'
FirebaseAuth-Swift.h:1560:162 unknown type name 'WKNavigationActionPolicy'
FirebaseAuth-Swift.h:1560:186 interface type 'WKNavigationAction' cannot be passed by value
SWIFT_CLASS("_TtC12FirebaseAuth16AuthURLPresenter")
@interface AuthURLPresenter : NSObject <SFSafariViewControllerDelegate>

SWIFT_CLASS("_TtC12FirebaseAuth21AuthWebViewController")
@interface AuthWebViewController : UIViewController <WKNavigationDelegate>

Diffing the generated header between the two configurations is more informative than the errors themselves:

APPLICATION_EXTENSION_API_ONLY = YES
  @import FirebaseAuthInterop, FirebaseCoreExtension, Foundation,
          ObjectiveC, SafariServices, UIKit, WebKit
  declares AuthURLPresenter       : SFSafariViewControllerDelegate
           AuthWebViewController  : WKNavigationDelegate
           FIRAuthComponent       : FIRComponentLifecycleMaintainer

APPLICATION_EXTENSION_API_ONLY = NO
  @import FirebaseAuthInterop, Foundation, ObjectiveC, UIKit
  AuthURLPresenter / AuthWebViewController / FIRAuthComponent — absent entirely

So under -application-extension the generated header gains declarations, and the matching @imports, that it otherwise omits — and those added declarations are what consumers cannot resolve. I don't have an explanation for why the extension-only build exposes more to Objective-C than the regular one; that seemed worth reporting even without a mechanism.

Reproduces byte-identically from a wiped DerivedData, so it isn't incremental-build state; I checked that first and was wrong about it.

I'm not sure whether this is a FirebaseAuth issue or a CocoaPods static-framework interaction on my side, so take it as a data point rather than a bug report. But the practical outcome is that with this patch applied, clearing APPLICATION_EXTENSION_API_ONLY on the pod is still the only thing that builds for me — which suggests UIApplication.shared was the first extension-safety break in 12.18.0 rather than the only one.

Happy to test a follow-up patch if useful.

@paulb777

paulb777 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Thanks for checking. We'll need to do some more digging. Here's Gemini's summary:

This is a fascinating edge case, and you've hit on a very obscure intersection between Swift's -Swift.h generation, CocoaPods static frameworks, and Objective-C modules.

Here is the exact mechanism of why the extension-only build exposes more to Objective-C than the regular one, and why it fails.

The Mechanism

1. The Swift Compiler "Executable" Quirk
When CocoaPods builds a static framework (due to use_frameworks! :linkage => :static), it compiles the pod into a static library. When APPLICATION_EXTENSION_API_ONLY = YES is applied, CocoaPods passes the -application-extension flag to the Swift compiler.
Under these specific conditions, the Swift compiler assumes it is compiling the App Extension executable itself, rather than a strict library module. For executables, Swift intentionally exports internal @objc classes to the generated -Swift.h header so the app's own Objective-C code can access them.
When APPLICATION_EXTENSION_API_ONLY = NO, Swift correctly treats the target as a library and strictly limits the -Swift.h header to public and open declarations, which is why those internal classes are "absent entirely" in your normal builds.

2. The SFSafariViewControllerDelegate Failure
Because the internal class AuthURLPresenter is now erroneously exported to -Swift.h, it brings <SFSafariViewControllerDelegate> with it. When the Objective-C compiler parses <SafariServices/SafariServices.h> with -fapplication-extension enabled, it actively strips out APIs marked API_UNAVAILABLE(extension). Since the delegate protocol is stripped, the Objective-C compiler throws the no type or protocol named 'SFSafariViewControllerDelegate' error. (Swift compiles fine initially because it's more lenient with unavailable protocols in class declarations until they are explicitly used).

3. The FIRComponentLifecycleMaintainer & WKNavigationDelegate Failure
These two protocols are not unavailable in app extensions. They fail because the Objective-C consumer importing your generated -Swift.h header likely has Objective-C Modules disabled (-fmodules is false).
The generated -Swift.h relies on @import FirebaseCoreExtension; and @import WebKit;. If modules are disabled in the consuming translation unit, those @import statements are skipped by the preprocessor. Since the umbrella header didn't manually #import them, the protocols remain completely undeclared, resulting in the error.

How the PR can fix this

Since the root issue is internal classes leaking into the public -Swift.h header, the PR will need to prevent their emission. Here are the most viable paths forward for the Firebase team:

Option A: Enforce Library Evolution (Build Settings)
Adding BUILD_LIBRARY_FOR_DISTRIBUTION = YES to FirebaseAuth's pod_target_xcconfig forces the Swift compiler to strictly enforce public API boundaries. This will hide internal classes from -Swift.h regardless of the app-extension flag. (Though this may have slight binary size implications that the Firebase team would need to weigh).

Option B: Use @available + Dynamic Dispatch (Code Level)
The PR can mark AuthURLPresenter and AuthWebViewController with @available(iOSApplicationExtension, unavailable). This definitively prevents the Swift compiler from emitting them for extension builds.
However, because Auth.swift (which must be available in extensions) instantiates AuthURLPresenter, the PR would need to instantiate it dynamically using NSClassFromString to avoid compile-time availability errors within Auth.swift itself.

Option C: Swift @_implementationOnly
If FirebaseCoreExtension and WebKit were imported using @_implementationOnly import, Swift would refuse to expose types from those modules in the generated Objective-C header. However, this is still considered a private Swift feature and wouldn't solve the SFSafariViewControllerDelegate issue since SafariServices might still be needed publicly elsewhere.

You are entirely correct that UIApplication.shared was simply the first extension-safety break. Fixing it unblocked the Swift compiler, which then successfully generated the -Swift.h header, revealing this secondary compiler emission bug.

@ncooke3

ncooke3 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Closing in favor of #16594. I've cherry-picked your commit into that one, @jeffwall-curlewlabs. This way, I could make some small revisions and get it merged for code freeze.

@ncooke3

ncooke3 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Pivoting back to this PR. The added test revisions caused more problems than they were worth.

@ncooke3
ncooke3 merged commit 344a361 into firebase:main Sep 2, 2026
80 checks passed
@ncooke3

ncooke3 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Thanks!

@ncooke3

ncooke3 commented Sep 2, 2026

Copy link
Copy Markdown
Member

Thanks for testing, @itamarbareket. I haven't been able to reproduce that particular issue. I created #16595, but am unsure if that'll makes a difference for you.

@jeffwall-curlewlabs

Copy link
Copy Markdown
Contributor Author

Thanks!

Happy to help! Thank you for creating the other test cases also. Great to get coverage for these cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FirebaseAuth] 12.18.0 regression: AuthNotificationManager uses UIApplication.shared directly, breaking app-extension builds

4 participants