wake-ios/wake/View/Components/AppleSignInButton.swift
2025-09-01 18:35:57 +08:00

271 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import AuthenticationServices
import CryptoKit
import os.log
/// Apple
struct AppleSignInButton: View {
// MARK: -
///
let onRequest: (ASAuthorizationAppleIDRequest) -> Void
///
let onCompletion: (Result<ASAuthorization, Error>) -> Void
///
let buttonText: String
//
private let logger = Logger(subsystem: "com.yourapp", category: "AppleSignInButton")
// coordinator
@State private var coordinator: Coordinator?
// MARK: -
init(buttonText: String = "Continue with Apple",
onRequest: @escaping (ASAuthorizationAppleIDRequest) -> Void,
onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void) {
self.buttonText = buttonText
self.onRequest = onRequest
self.onCompletion = onCompletion
logger.debug("AppleSignInButton 初始化,按钮文字: \(buttonText)")
}
// MARK: -
var body: some View {
Button(action: handleSignIn) {
HStack(alignment: .center, spacing: 8) {
Image(systemName: "applelogo")
.font(.system(size: 20, weight: .regular))
Text(buttonText)
.font(.system(size: 18, weight: .regular))
}
.frame(maxWidth: .infinity)
.frame(height: 60)
.background(Color.white)
.foregroundColor(.black)
.cornerRadius(30)
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.black, lineWidth: 1)
)
}
}
// MARK: -
private func handleSignIn() {
logger.debug("🍎 用户点击了Apple登录按钮")
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
// nonce
let nonce = randomNonceString(length: 32)
logger.debug("🔑 生成Nonce: \(nonce)")
request.nonce = sha256(nonce)
logger.debug("🔐 Nonce的SHA256: \(request.nonce ?? "nil")")
//
logger.debug("📞 调用onRequest回调")
onRequest(request)
//
logger.debug("🔄 创建ASAuthorizationController")
let controller = ASAuthorizationController(authorizationRequests: [request])
// presentation context provider
let presentationContextProvider = PresentationContextProvider()
// coordinator
let newCoordinator = Coordinator(
onCompletion: { [logger] result in
// coordinator
DispatchQueue.main.async {
self.coordinator = nil
}
switch result {
case .success(let auth):
logger.debug("✅ 授权成功 - 开始处理授权响应")
if let appleIDCredential = auth.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
logger.debug("👤 用户标识符: \(userIdentifier)")
// ID
UserDefaults.standard.set(userIdentifier, forKey: "appleAuthorizedUserIdKey")
}
case .failure(let error):
let nsError = error as NSError
logger.error("❌ 授权失败: \(nsError.localizedDescription)")
}
//
self.onCompletion(result)
},
logger: logger
)
// coordinator
self.coordinator = newCoordinator
// presentation context provider
controller.delegate = newCoordinator
controller.presentationContextProvider = presentationContextProvider
//
logger.debug("🚀 开始执行授权请求...")
DispatchQueue.main.async {
controller.performRequests()
}
}
private func logAppleIDError(_ error: ASAuthorizationError, logger: Logger) {
switch error.code {
case .canceled:
logger.error("❌ 用户取消了授权")
case .failed:
logger.error("❌ 授权请求失败")
case .invalidResponse:
logger.error("❌ 无效的授权响应")
case .notHandled:
logger.error("❌ 授权请求未被处理")
case .unknown:
logger.error("❌ 未知的授权错误")
@unknown default:
logger.error("❌ 未处理的授权错误")
}
}
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
return hashedData.compactMap { String(format: "%02x", $0) }.joined()
}
// 使
private func randomNonceString(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
// MARK: - Coordinator
private class Coordinator: NSObject, ASAuthorizationControllerDelegate {
let onCompletion: (Result<ASAuthorization, Error>) -> Void
private let logger: Logger
init(onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void, logger: Logger) {
self.onCompletion = onCompletion
self.logger = logger
super.init()
logger.debug("Coordinator 初始化")
}
//
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
logger.debug("✅ 授权成功 - 开始处理授权响应")
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
//
let userIdentifier = appleIDCredential.user
logger.debug("👤 用户标识符: \(userIdentifier)")
//
if let email = appleIDCredential.email, let fullName = appleIDCredential.fullName {
logger.debug("👋 检测到新用户")
logger.debug("📧 新用户邮箱: \(email)")
logger.debug("👤 新用户全名: \(fullName.givenName ?? "") \(fullName.familyName ?? "")")
} else {
logger.debug("👋 现有用户登录")
}
// UserDefaults
UserDefaults.standard.set(userIdentifier, forKey: "appleAuthorizedUserIdKey")
//
if let authCodeData = appleIDCredential.authorizationCode,
let _ = String(data: authCodeData, encoding: .utf8) {
logger.debug("🔑 成功获取授权码")
} else {
logger.warning("⚠️ 未获取到授权码")
}
//
if let identityTokenData = appleIDCredential.identityToken,
let _ = String(data: identityTokenData, encoding: .utf8) {
logger.debug("🔐 成功获取身份令牌")
} else {
logger.error("❌ 获取身份令牌失败")
}
} else {
logger.error("❌ 无法获取有效的 Apple ID 凭证")
}
//
logger.debug("🔄 调用完成处理程序")
onCompletion(.success(authorization))
}
//
func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
let nsError = error as NSError
logger.error("❌ 授权失败: \(nsError.localizedDescription)")
logger.error("错误域: \(nsError.domain), 错误码: \(nsError.code)")
if let appleIDError = error as? ASAuthorizationError {
switch appleIDError.code {
case .canceled:
logger.error("❌ 用户取消了授权")
case .failed:
logger.error("❌ 授权请求失败")
case .invalidResponse:
logger.error("❌ 无效的授权响应")
case .notHandled:
logger.error("❌ 授权请求未被处理")
case .unknown:
logger.error("❌ 未知的授权错误")
@unknown default:
logger.error("❌ 未处理的授权错误")
}
//
if let errorString = (appleIDError as NSError).userInfo[NSDebugDescriptionErrorKey] as? String {
logger.error("🔍 错误详情: \(errorString)")
}
}
//
logger.debug("🔄 调用完成处理程序(错误)")
onCompletion(.failure(error))
}
}
// MARK: - Presentation Context Provider
private class PresentationContextProvider: NSObject, ASAuthorizationControllerPresentationContextProviding {
private let logger = Logger(subsystem: "com.yourapp.applesignin", category: "PresentationContextProvider")
override init() {
super.init()
logger.debug("PresentationContextProvider 初始化")
}
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
logger.debug("获取presentation anchor")
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
logger.error("无法获取key window将使用第一个window")
return UIApplication.shared.windows.first ?? UIWindow()
}
return window
}
}
}