Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Oreo-Mcflurry opened this issue Oct 28, 2024 · 2 comments · Fixed by #13984
Closed

Comments

@Oreo-Mcflurry
Copy link

Oreo-Mcflurry commented Oct 28, 2024

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!
@google-oss-bot
Copy link

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@paulb777
Copy link
Member

Thanks for the report. It looks like an issue in the community supported Combine library

@firebase firebase locked and limited conversation to collaborators Nov 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants