Skip to content

Firebase 11 Version WatchOS Bug (Cannot find type 'GameCenterAuthProvider' in scope) #13979

@Oreo-Mcflurry

Description

@Oreo-Mcflurry

Description

Xcode Version: 15.2
Minimum Target(iOS): 16.1
Minimun Target(watchOS): 9.0

The GameCenterAuthProvider is currently not defined for watchOS, but its extension targets watchOS, which is causing issues when trying to build Firebase for watchOS.

Temporary Workaround:
install Firebase 10.29.0 Version.

Problem:

•	The FirebaseAuth library includes GameCenterAuthProvider functionality, but it appears that the core provider itself does not support watchOS.
•	However, the GameCenterAuthProvider extension is set to target watchOS, which leads to build errors when the library is used in a watchOS project.

Expected Behavior:

•	Either GameCenterAuthProvider should be defined and supported for watchOS, or the extension targeting should exclude watchOS to avoid any conflicts in watchOS builds.

Steps to Reproduce:

1.	Add FirebaseAuth as a dependency using SPM in a project with both iOS and watchOS targets.
2.	Attempt to build the project for watchOS.
3.	Observe the build error related to GameCenterAuthProvider.

Code:

 Error Here ::>>
#if canImport(Combine) && swift(>=5.0)

  import Combine
  import FirebaseAuth

  @available(swift 5.0)
  @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, *)
  @available(watchOS, unavailable)
  public extension GameCenterAuthProvider {      // Error: Cannot find type 'GameCenterAuthProvider' in scope
    /// Creates an `AuthCredential` for a Game Center sign in.
    ///
    /// The publisher will emit events on the **main** thread.
    ///
    /// - Returns: A publisher that emits an `AuthCredential` when the credential is obtained
    ///   successfully, or an error otherwise. The publisher will emit on the *main* thread.
    class func getCredential() -> Future<AuthCredential, Error> {
      Future<AuthCredential, Error> { promise in
        self.getCredential { authCredential, error in
          if let error {
            promise(.failure(error))
          } else if let authCredential {
            promise(.success(authCredential))
          }
        }
      }
    }
  }

#endif // canImport(Combine) && swift(>=5.0)

GameCenterAuthProvider Defined ::>>

#if !os(watchOS)
  import Foundation
  import GameKit

  // TODO: Delete this when minimum iOS version passes 13.5.
  /// WarningWorkaround is needed because playerID is deprecated in iOS 13.0 but still needed until
  /// 13.5 when the fetchItems API was introduced.
  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  private protocol WarningWorkaround {
    static func pre135Credential(localPlayer: GKLocalPlayer,
                                 completion: @escaping (AuthCredential?, Error?) -> Void)
  }

  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  extension GameCenterAuthProvider: WarningWorkaround {}

  /// A concrete implementation of `AuthProvider` for Game Center Sign In. Not available on watchOS.
  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  @objc(FIRGameCenterAuthProvider) open class GameCenterAuthProvider: NSObject {
    /// A string constant identifying the Game Center identity provider.
    @objc public static let id = "gc.apple.com"

    /// Creates an `AuthCredential` for a Game Center sign in.
    @objc open class func getCredential(completion: @escaping (AuthCredential?, Error?) -> Void) {
      /**
       Linking GameKit.framework without using it on macOS results in App Store rejection.
       Thus we don't link GameKit.framework to our SDK directly. `optionalLocalPlayer` is used for
       checking whether the APP that consuming our SDK has linked GameKit.framework. If not, a
       `GameKitNotLinkedError` will be raised.
       **/
      guard let _: AnyClass = NSClassFromString("GKLocalPlayer") else {
        completion(nil, AuthErrorUtils.gameKitNotLinkedError())
        return
      }

      let localPlayer = GKLocalPlayer.local
      guard localPlayer.isAuthenticated else {
        completion(nil, AuthErrorUtils.localPlayerNotAuthenticatedError())
        return
      }

      if #available(iOS 13.5, macOS 10.15.5, macCatalyst 13.5, tvOS 13.4.8, *) {
        localPlayer.fetchItems { publicKeyURL, signature, salt, timestamp, error in
          if let error = error {
            completion(nil, error)
          } else {
            let credential = GameCenterAuthCredential(withPlayerID: "",
                                                      teamPlayerID: localPlayer.teamPlayerID,
                                                      gamePlayerID: localPlayer.gamePlayerID,
                                                      publicKeyURL: publicKeyURL,
                                                      signature: signature,
                                                      salt: salt,
                                                      timestamp: timestamp,
                                                      displayName: localPlayer.displayName)
            completion(credential, nil)
          }
        }
      } else {
        (GameCenterAuthProvider.self as WarningWorkaround.Type).pre135Credential(
          localPlayer: localPlayer, completion: completion
        )
      }
    }

    @available(iOS, deprecated: 13.0)
    @available(tvOS, deprecated: 13.0)
    @available(macOS, deprecated: 10.15.0)
    @available(macCatalyst, deprecated: 13.0)
    fileprivate class func pre135Credential(localPlayer: GKLocalPlayer,
                                            completion: @escaping (AuthCredential?, Error?)
                                              -> Void) {
      localPlayer
        .generateIdentityVerificationSignature { publicKeyURL, signature, salt, timestamp, error in
          if error != nil {
            completion(nil, error)
          } else {
            /**
             `localPlayer.alias` is actually the displayname needed, instead of
             `localPlayer.displayname`. For more information, check
             https://developer.apple.com/documentation/gamekit/gkplayer
             **/
            let displayName = localPlayer.alias
            let credential = GameCenterAuthCredential(withPlayerID: localPlayer.playerID,
                                                      teamPlayerID: nil,
                                                      gamePlayerID: nil,
                                                      publicKeyURL: publicKeyURL,
                                                      signature: signature,
                                                      salt: salt,
                                                      timestamp: timestamp,
                                                      displayName: displayName)
            completion(credential, nil)
          }
        }
    }

    /// Creates an `AuthCredential` for a Game Center sign in.
    @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
    open class func getCredential() async throws -> AuthCredential {
      return try await withCheckedThrowingContinuation { continuation in
        getCredential { credential, error in
          if let credential = credential {
            continuation.resume(returning: credential)
          } else {
            continuation.resume(throwing: error!) // TODO: Change to ?? and generate unknown error
          }
        }
      }
    }

    @available(*, unavailable)
    @objc override public init() {
      fatalError("This class is not meant to be initialized.")
    }
  }

  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  @objc(FIRGameCenterAuthCredential)
  class GameCenterAuthCredential: AuthCredential, NSSecureCoding {
    let playerID: String
    let teamPlayerID: String?
    let gamePlayerID: String?
    let publicKeyURL: URL?
    let signature: Data?
    let salt: Data?
    let timestamp: UInt64
    let displayName: String

    /// - Parameter playerID: The ID of the Game Center local player.
    /// - Parameter teamPlayerID: The teamPlayerID of the Game Center local player.
    /// - Parameter gamePlayerID: The gamePlayerID of the Game Center local player.
    /// - Parameter publicKeyURL: The URL for the public encryption key.
    /// - Parameter signature: The verification signature generated.
    /// - Parameter salt: A random string used to compute the hash and keep it randomized.
    /// - Parameter timestamp: The date and time that the signature was created.
    /// - Parameter displayName: The display name of the Game Center player.
    init(withPlayerID playerID: String, teamPlayerID: String?, gamePlayerID: String?,
         publicKeyURL: URL?, signature: Data?, salt: Data?,
         timestamp: UInt64, displayName: String) {
      self.playerID = playerID
      self.teamPlayerID = teamPlayerID
      self.gamePlayerID = gamePlayerID
      self.publicKeyURL = publicKeyURL
      self.signature = signature
      self.salt = salt
      self.timestamp = timestamp
      self.displayName = displayName
      super.init(provider: GameCenterAuthProvider.id)
    }

    // MARK: Secure Coding

    static var supportsSecureCoding = true

    func encode(with coder: NSCoder) {
      coder.encode(playerID, forKey: "playerID")
      coder.encode(teamPlayerID, forKey: "teamPlayerID")
      coder.encode(gamePlayerID, forKey: "gamePlayerID")
      coder.encode(publicKeyURL, forKey: "publicKeyURL")
      coder.encode(signature, forKey: "signature")
      coder.encode(salt, forKey: "salt")
      coder.encode(timestamp, forKey: "timestamp")
      coder.encode(displayName, forKey: "displayName")
    }

    required init?(coder: NSCoder) {
      guard let playerID = coder.decodeObject(of: NSString.self, forKey: "playerID") as? String,
            let teamPlayerID = coder.decodeObject(
              of: NSString.self,
              forKey: "teamPlayerID"
            ) as? String,
            let gamePlayerID = coder.decodeObject(
              of: NSString.self,
              forKey: "gamePlayerID"
            ) as? String,
            let timestamp = coder.decodeObject(of: NSNumber.self, forKey: "timestamp") as? UInt64,
            let displayName = coder.decodeObject(
              of: NSString.self,
              forKey: "displayName"
            ) as? String else {
        return nil
      }
      self.playerID = playerID
      self.teamPlayerID = teamPlayerID
      self.gamePlayerID = gamePlayerID
      self.timestamp = timestamp
      self.displayName = displayName
      publicKeyURL = coder.decodeObject(forKey: "publicKeyURL") as? URL
      signature = coder.decodeObject(of: NSData.self, forKey: "signature") as? Data
      salt = coder.decodeObject(of: NSData.self, forKey: "salt") as? Data
      super.init(provider: GameCenterAuthProvider.id)
    }
  }
#endif

Reproducing the issue

No response

Firebase SDK Version

11.4

Xcode Version

15.2

Installation Method

Swift Package Manager

Firebase Product(s)

Authentication

Targeted Platforms

iOS, watchOS

Relevant Log Output

No response

If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet
Replace this line with the contents of your Package.resolved.

If using CocoaPods, the project's Podfile.lock

Expand Podfile.lock snippet
Replace this line with the contents of your Podfile.lock!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions