Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FirebaseStorageUI/Sources/FUIStorageImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ - (BOOL)canRequestImageForURL:(NSURL *)url {
BOOL finished = receivedSize >= expectedSize;
if (coderQueue.operationCount == 0) {
[coderQueue addOperationWithBlock:^{
UIImage *image = SDImageLoaderDecodeProgressiveImageData(partialData, url, finished, task, options, context);
UIImage *image = SDImageLoaderDecodeProgressiveImageData(partialData, url, finished, (id<SDWebImageOperation>)task, options, context);
if (image) {
dispatch_main_async_safe(^{
if (completedBlock) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import FirebaseAuth
import SwiftUI

/// Context information for OAuth provider reauthentication (Google, Apple, Facebook, Twitter, etc.)
public struct OAuthReauthContext: Equatable {
public struct OAuthReauthContext: Equatable, Sendable {
public let providerId: String
public let providerName: String

Expand All @@ -31,7 +31,7 @@ public struct OAuthReauthContext: Equatable {
}

/// Context information for email/password reauthentication
public struct EmailReauthContext: Equatable {
public struct EmailReauthContext: Equatable, Sendable {
public let email: String

public init(email: String) {
Expand All @@ -44,7 +44,7 @@ public struct EmailReauthContext: Equatable {
}

/// Context information for email link reauthentication
public struct EmailLinkReauthContext: Equatable {
public struct EmailLinkReauthContext: Equatable, Sendable {
public let email: String

public init(email: String) {
Expand All @@ -57,7 +57,7 @@ public struct EmailLinkReauthContext: Equatable {
}

/// Context information for phone number reauthentication
public struct PhoneReauthContext: Equatable {
public struct PhoneReauthContext: Equatable, Sendable {
public let phoneNumber: String

public init(phoneNumber: String) {
Expand All @@ -70,7 +70,7 @@ public struct PhoneReauthContext: Equatable {
}

/// Type-safe wrapper for reauthentication contexts
public enum ReauthenticationType: Equatable {
public enum ReauthenticationType: Equatable, Sendable {
case oauth(OAuthReauthContext)
case email(EmailReauthContext)
case emailLink(EmailLinkReauthContext)
Expand All @@ -91,7 +91,7 @@ public enum ReauthenticationType: Equatable {
}

/// Describes the specific type of account conflict that occurred
public enum AccountConflictType: Equatable {
public enum AccountConflictType: Equatable, Sendable {
/// Account exists with a different provider (e.g., user signed up with Google, trying to use
/// email)
/// Solution: Sign in with existing provider, then link the new credential
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public final class AuthService {
}

public func signOut() async throws {
try await auth.signOut()
try auth.signOut()
// Cannot wait for auth listener to change, feedback needs to be immediate
currentUser = nil
// Clear email link sign-in flag
Expand All @@ -239,7 +239,7 @@ public final class AuthService {
updateAuthenticationState()
}

public func linkAccounts(credentials credentials: AuthCredential) async throws {
public func linkAccounts(credentials: AuthCredential) async throws {
authenticationState = .authenticating
do {
guard let user = currentUser else {
Expand Down Expand Up @@ -414,7 +414,7 @@ public extension AuthService {
}
}

func createUser(email email: String, password: String) async throws -> SignInOutcome {
func createUser(email: String, password: String) async throws -> SignInOutcome {
authenticationState = .authenticating
let credential = EmailAuthProvider.credential(withEmail: email, password: password)

Expand Down Expand Up @@ -474,7 +474,7 @@ public extension AuthService {
}
}

func handleSignInLink(url url: URL) async throws {
func handleSignInLink(url: URL) async throws {
do {
// Check which flow we're in based on the flag
let email: String
Expand Down Expand Up @@ -532,9 +532,9 @@ public extension AuthService {
paramName: "ui_auid"
)
if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser?.uid {
try await handleAutoUpgradeAnonymousUser(credentials: credential)
_ = try await handleAutoUpgradeAnonymousUser(credentials: credential)
} else {
let result = try await auth.signIn(withEmail: email, link: link)
_ = try await auth.signIn(withEmail: email, link: link)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action required, but maybe we could annotate these with allow-unused-result upstream.

}
updateAuthenticationState()
// Track that user signed in with email link
Expand Down Expand Up @@ -586,7 +586,7 @@ public extension AuthService {
func signInWithPhoneNumber(verificationID: String, verificationCode: String) async throws {
let credential = PhoneAuthProvider.provider()
.credential(withVerificationID: verificationID, verificationCode: verificationCode)
try await signIn(credentials: credential)
_ = try await signIn(credentials: credential)
}
}

Expand Down Expand Up @@ -758,7 +758,7 @@ public extension AuthService {
}

do {
let result = try await resolver.resolveSignIn(with: assertion)
_ = try await resolver.resolveSignIn(with: assertion)
updateAuthenticationState()

// Clear MFA resolution state
Expand Down Expand Up @@ -792,8 +792,6 @@ public extension AuthService {
throw AuthServiceError.noCurrentUser
}

let multiFactorUser = user.multiFactor

// Create the appropriate assertion based on factor type
let assertion: MultiFactorAssertion

Expand All @@ -814,7 +812,7 @@ public extension AuthService {

case .totp:
// For TOTP, we need the secret from the session
guard let totpInfo = session.totpInfo else {
guard session.totpInfo != nil else {
throw AuthServiceError
.multiFactorAuth("TOTP info is missing from enrollment session")
}
Expand Down Expand Up @@ -905,8 +903,8 @@ public extension AuthService {

// MARK: - Private Helper Methods

private extension AuthService {
internal func updateAuthenticationState() {
extension AuthService {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the agent

Note (Verified Safe, Optional Cleanup): Checked all remaining properties and methods inside this extension AuthService block (lines 918–1190) — every one explicitly declares private, so removing private from extension does not accidentally expose any private helper as internal. However, moving func updateAuthenticationState() out above private extension AuthService would keep private extension AuthService intact so any future helper added under // MARK: - Private Helper Methods stays private by default.

func updateAuthenticationState() {
authenticationState =
(currentUser == nil || currentUser?.isAnonymous == true)
? .unauthenticated
Expand Down Expand Up @@ -1090,7 +1088,8 @@ private extension AuthService {

private func buildLegacySignInRecovery(email: String,
signInMethods: [String],
attemptedProviderId: String?) -> LegacySignInRecoveryContext? {
attemptedProviderId: String?)
-> LegacySignInRecoveryContext? {
let attemptedOptionId = attemptedProviderId.map(normalizeLegacyOptionId)
var options: [LegacySignInOption] = []
var unavailableProviders: [String] = []
Expand Down Expand Up @@ -1137,7 +1136,9 @@ private extension AuthService {
displayName: string.legacyEmailLinkOptionLabel
)
default:
guard let provider = providers.first(where: { $0.id == normalizeLegacyOptionId(signInMethod) }) else {
guard let provider = providers.first(where: {
$0.id == normalizeLegacyOptionId(signInMethod)
}) else {
return nil
}
return LegacySignInOption(id: provider.id, displayName: provider.displayName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import FirebaseAuth
import FirebaseCore
import SwiftUI

extension MultiFactorInfo: Identifiable {
extension MultiFactorInfo: @retroactive Identifiable {
public var id: String { uid }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class FacebookProviderSwift: CredentialAuthProviderSwift {
.providerAuthenticationFailed("Failed to create Facebook login configuration")
}

let result = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
Void,
Error
>) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class GoogleProviderSwift: CredentialAuthProviderSwift {
}

@MainActor public func createAuthCredential() async throws -> AuthCredential {
guard let presentingViewController = await (UIApplication.shared.connectedScenes
guard let presentingViewController = (UIApplication.shared.connectedScenes
.first as? UIWindowScene)?.windows.first?.rootViewController else {
throw AuthServiceError
.rootViewControllerNotFound(
Expand Down
6 changes: 3 additions & 3 deletions FirebaseSwiftUI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,7 +1178,7 @@ Signs in using email and password credentials.
##### Create User with Email/Password

```swift
public func createUser(email email: String, password: String) async throws -> SignInOutcome
public func createUser(email: String, password: String) async throws -> SignInOutcome
```

Creates a new user account with email and password.
Expand Down Expand Up @@ -1218,7 +1218,7 @@ Button("Sign Out") {
##### Link Accounts

```swift
public func linkAccounts(credentials credentials: AuthCredential) async throws
public func linkAccounts(credentials: AuthCredential) async throws
```

Links a new authentication method to the current user's account.
Expand Down Expand Up @@ -1255,7 +1255,7 @@ Sends a sign-in link to the specified email address. Can also be used for reauth
##### Handle Sign-In Link

```swift
public func handleSignInLink(url url: URL) async throws
public func handleSignInLink(url: URL) async throws
```

Handles the email link flow when the user taps the link. Automatically routes to either sign-in or reauthentication based on the current context.
Expand Down
30 changes: 15 additions & 15 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ import PackageDescription
let package = Package(
name: "FirebaseUI",
defaultLocalization: "en",
platforms: [.iOS(.v17)],
platforms: [
.iOS(.v17),
// Satisfies SPM's dependency graph check for FirebaseAuth 13 & GoogleSignIn 11.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be GSI 10

// This package only supports iOS.
.macOS(.v12),
],
Comment on lines +23 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Adding .macOS(.v12) to the supported platforms will cause compilation failures when building for macOS. Several targets in this package (such as FirebaseAuthSwiftUI, FirebaseGoogleSwiftUI, and FirebaseStorageUI) contain iOS-only APIs (like UIApplication, UIWindowScene, and UIImage) and do not have #if os(iOS) or #if canImport(UIKit) guards. If this package must declare macOS support to satisfy dependency graph checks, you will need to wrap all iOS-specific source files and APIs in #if os(iOS) guards to prevent compilation errors on macOS. Otherwise, it is highly recommended to revert this change and have consuming applications conditionally depend on this package only for their iOS targets.

  platforms: [.iOS(.v17)],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

• SPM's Fallback Behavior: When a platform is omitted from platforms: [...] in Package.swift, SPM does not interpret it as "this platform is unsupported". Instead, it falls back to the default minimum
version for that platform based on your swift-tools-version. For tools 6.0, that default is macOS 10.13.
• Dependency Graph Floor Check: SPM enforces that a target's deployment target must be ≥ its dependencies' deployment targets across all platforms SPM thinks the target supports.
• GoogleSignIn requires macOS 12.0 (and FirebaseAuth requires macOS 11.0 in Firebase 13).
• Because FirebaseUI only listed .iOS(.v17), SPM assigned it the default macOS 10.13, saw 10.13 < 12.0, and failed graph validation before building.
• Declaring .macOS(.v12) explicitly raises SPM's macOS floor above GoogleSignIn's requirement so graph validation passes.

@ncooke3 ncooke3 Oct 9, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library didn't work on macOS before this PR, so there's no difference in outcome.

products: [
.library(
name: "FirebaseDatabaseUI",
Expand Down Expand Up @@ -74,15 +79,11 @@ let package = Package(
),
.package(
url: "https://github.com/firebase/firebase-ios-sdk.git",
"8.0.0" ..< "13.0.0"
"8.0.0" ..< "14.0.0"
),
.package(
url: "https://github.com/google/GoogleSignIn-iOS",
from: "10.0.0"
),
.package(
url: "https://github.com/google/GoogleUtilities.git",
"7.4.1" ..< "9.0.0"
"7.0.0" ..< "11.0.0"
),
.package(
url: "https://github.com/SDWebImage/SDWebImage.git",
Expand Down
11 changes: 8 additions & 3 deletions okf-bundle/packaging/spm-and-cocoapods-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ Canonical consumer docs: [`README.md`](../../README.md), [`GETTING_STARTED.md`](
1. **SwiftUI Auth is SPM-first.** Do not add CocoaPods subspecs for `FirebaseAuthSwiftUI` / provider packages unless durable docs and CI are updated in the same change.
2. **UIKit data-binding modules are dual-built.** Edits under `Firebase*UI/Sources` must keep CocoaPods tests and SPM scheme builds green ([distribution path gate](../testing/running-tests.md#distribution-path-gate-blocking)).
3. **One source tree per module.** Do not fork SPM-only copies of UIKit sources; `Package.swift` targets point at the same `Sources` directories as the pods.
4. **Versioning is split operationally:**
- CocoaPods / umbrella: [`release.sh`](../../release.sh), [`staging.sh`](../../staging.sh), podspecs
- SwiftUI Auth package version stamp: [`release-swift.sh`](../../release-swift.sh) updates `FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Version.swift` (human-driven; agents need explicit user request)
4. **Versioning and releases:**
- CocoaPods / umbrella: Starting with Firebase 13 and FirebaseUI-iOS 16.2,
podspecs are frozen and no longer published to CocoaPods trunk.
- SwiftUI Auth package version stamp:
[`release-swift.sh`](../../release-swift.sh) updates
`FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Version.swift` (human-driven;
agents need explicit user request).
5. **Swift language mode:** SwiftUI Auth targets set `.swiftLanguageMode(.v6)` in `Package.swift`. Do not silently relax to silence concurrency errors — fix or record an accepted exception.

## `Package.swift` ownership
Expand All @@ -56,5 +60,6 @@ Canonical consumer docs: [`README.md`](../../README.md), [`GETTING_STARTED.md`](
| Dist-AD-1 | SwiftUI Auth ships via SPM; UIKit Database/Firestore/Storage remain CocoaPods-supported and SPM-listed | Accepted |
| Dist-AD-2 | Shared UIKit sources must pass both CocoaPods `./test.sh` and CI SPM scheme jobs when changed | Accepted |
| Dist-AD-3 | Agents do not run trunk/release scripts without explicit user request | Accepted |
| Dist-AD-4 | Starting with Firebase 13 and FirebaseUI-iOS 16.2, CocoaPods podspecs are frozen and no longer published to trunk | Accepted |

New distribution decisions get a row here; active migration tasks go in the [work queue](spm-cocoapods-work-queue.md).
4 changes: 2 additions & 2 deletions okf-bundle/testing/agent-command-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Single source for **which shell commands agents may run** in this repo. Test det
| SPM SwiftUI package unit tests | `./swiftui-tests.sh --unit` (preferred) — wraps `xcodebuild test -scheme FirebaseUI-Package` | `swift test` (this package is iOS-simulator backed) |
| Sample app builds | allowlisted sample `xcodebuild` in [running tests § samples](running-tests.md#sample-builds) | opening Xcode GUI as the agent gate |
| Podspec lint (touched pod) | `bundle exec pod lib lint <Pod>.podspec` (matches CI `pod` jobs) | `pod trunk push` / release scripts during normal PR work |
| Release / staging | **human-only** — [`release.sh`](../../release.sh), [`release-swift.sh`](../../release-swift.sh), [`staging.sh`](../../staging.sh) | agents must not run trunk/push release scripts unless the user explicitly requests |
| Release / staging | **human-only** — [`release-swift.sh`](../../release-swift.sh) | agents must not run trunk/push release scripts unless the user explicitly requests |

### Host / toolchain assumptions

Expand All @@ -53,7 +53,7 @@ Single source for **which shell commands agents may run** in this repo. Test det
| `swift test` for this package | Targets are iOS; CI uses `xcodebuild test` on simulator |
| `npx` / `yarn` / `npm test` as product validation | Not this repo's test entrypoints (Firebase CLI/`npm` only for Auth emulator via documented scripts) |
| Invented SwiftLint / clang-format agent gates | Swift lint entrypoint is `./lint-swift.sh`; ObjC style is Google Objective-C guide via review, not an agent script today |
| `pod trunk push`, `./release.sh`, `./release-swift.sh` without explicit user request | Publishes artifacts |
| `pod trunk push`, `./release-swift.sh` without explicit user request | Publishes artifacts |
| Changing `Package.resolved` / lockfiles to silence dependency errors without product justification | Masks real resolution issues |
| Copying RNFB `yarn tests:*` / Detox / Jet commands | Wrong stack |
| Parallel overlapping simulator test runs on one host | Flaky boots and port contention (Auth emulator `:9099`) |
Expand Down
Loading
Loading