Capacitor iOS:如何从原生ViewController向JS返回Passkey认证结果
解决Capacitor中Passkey凭证从原生ViewController传回JS的问题
核心问题是你当前在someFunction里同步调用了call.resolve,但Passkey认证是异步操作,必须等凭证获取完成后再执行call.resolve。以下是具体修改步骤:
1. 修改SamplePlugin.swift:保存PluginCall并添加回调逻辑
需要把CAPPluginCall保存为实例变量,同时给SignInViewController添加回调闭包,用于接收凭证数据:
import Foundation import Capacitor @objc(SamplePlugin) public class SamplePlugin: CAPPlugin { // 保存当前的PluginCall,等待异步操作完成后处理 private var currentCall: CAPPluginCall? @objc func someFunction(_ call: CAPPluginCall) { let value = call.getString("value") ?? "" let value2 = call.getString("value2") ?? "" guard let challengeStr = call.getString("challenge"), let challenge = challengeStr.data(using: .utf8) else { call.reject("Invalid challenge parameter") return } // 保存当前call currentCall = call DispatchQueue.main.async { let signInViewController = SignInViewController(challenge: challenge) // 添加凭证接收回调 signInViewController.onCredentialReceived = { [weak self] credentialData in guard let self = self, let call = self.currentCall else { return } signInViewController.dismiss(animated: true) // 把凭证和其他参数一起传回JS call.resolve([ "value": value, "testval": value2, "platform": "ios", "credential": credentialData ]) self.currentCall = nil } // 添加取消操作回调 signInViewController.onCancel = { [weak self] in guard let self = self, let call = self.currentCall else { return } signInViewController.dismiss(animated: true) call.reject("Sign-in canceled by user") self.currentCall = nil } self.bridge?.viewController?.present(signInViewController, animated: true, completion: nil) } } }
2. 修改SignInViewController.swift:添加回调闭包并接收凭证数据
给ViewController添加两个回调闭包,分别处理成功获取凭证和取消操作,同时修改通知监听逻辑接收AccountManager传来的凭证:
class SignInViewController: UIViewController { var challenge: Data! // 成功获取凭证的回调 var onCredentialReceived: (([String: Any]) -> Void)? // 取消操作的回调 var onCancel: (() -> Void)? private var signInObserver: NSObjectProtocol? private var signInErrorObserver: NSObjectProtocol? init(challenge: Data) { self.challenge = challenge super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 修改监听,接收通知携带的凭证数据 signInObserver = NotificationCenter.default.addObserver(forName: .UserSignedIn, object: nil, queue: nil) { [weak self] notification in guard let self = self, let credentialData = notification.object as? [String: Any] else { return } self.didFinishSignIn(with: credentialData) } signInErrorObserver = NotificationCenter.default.addObserver(forName: .ModalSignInSheetCanceled, object: nil, queue: nil) { [weak self] _ in self?.onCancel?() } guard let window = self.view.window else { fatalError("The view was not in the app's view hierarchy!") } let userName = "Name" (UIApplication.shared.delegate as? AppDelegate)?.accountManager.signUpWith(userName: userName, challenge: self.challenge, anchor: window) } func didFinishSignIn(with credentialData: [String: Any]) { // 调用回调把凭证传回Plugin onCredentialReceived?(credentialData) } // 移除通知监听,避免内存泄漏 deinit { if let observer = signInObserver { NotificationCenter.default.removeObserver(observer) } if let observer = signInErrorObserver { NotificationCenter.default.removeObserver(observer) } } }
3. 修改AccountManager.swift:传递凭证数据到通知
在获取到Passkey凭证后,把凭证转换为可序列化的字典,通过Notification传递给ViewController:
import AuthenticationServices import Foundation import os extension NSNotification.Name { static let UserSignedIn = Notification.Name("UserSignedInNotification") static let ModalSignInSheetCanceled = Notification.Name("ModalSignInSheetCanceledNotification") } class AccountManager: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate { let domain = "com.domain.app" var authenticationAnchor: ASPresentationAnchor? var isPerformingModalReqest = false func signUpWith(userName: String, challenge: Data, anchor: ASPresentationAnchor) { self.authenticationAnchor = anchor if #available(iOS 15.0, *) { let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain) let userID = "d0a4bc91-2def-4567-8983-9188a4ca2048".data(using: .utf8)! let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge, name: "User Fullname", userID: userID) let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] ) authController.delegate = self authController.presentationContextProvider = self authController.performRequests() isPerformingModalReqest = true } else { // 早期版本兼容 } } func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { isPerformingModalReqest = false if #available(iOS 15.0, *) { let logger = Logger() var credentialData: [String: Any] = [:] switch authorization.credential { case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration: logger.log("A new passkey was registered: \(credentialRegistration)") // 将凭证转换为可序列化的字典 credentialData = [ "type": "registration", "userID": credentialRegistration.userID.base64EncodedString(), "rawID": credentialRegistration.rawID.base64EncodedString(), "attestationObject": credentialRegistration.attestationObject.base64EncodedString(), "clientDataJSON": credentialRegistration.clientDataJSON.base64EncodedString() ] case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion: logger.log("A passkey was used to sign in: \(credentialAssertion)") credentialData = [ "type": "assertion", "userID": credentialAssertion.userID.base64EncodedString(), "rawID": credentialAssertion.rawID.base64EncodedString(), "authenticatorData": credentialAssertion.authenticatorData.base64EncodedString(), "signature": credentialAssertion.signature.base64EncodedString(), "clientDataJSON": credentialAssertion.clientDataJSON.base64EncodedString() ] case let passwordCredential as ASPasswordCredential: logger.log("A password was provided: \(passwordCredential)") credentialData = [ "type": "password", "user": passwordCredential.user, "password": passwordCredential.password ] default: fatalError("Received unknown authorization type.") } // 把凭证数据传递给回调 didFinishSignIn(with: credentialData) } else { // 早期版本兼容 } } // 修改方法,接收凭证数据并通过通知传递 func didFinishSignIn(with credentialData: [String: Any]) { print("didFinish") NotificationCenter.default.post(name: .UserSignedIn, object: credentialData) } // 处理授权失败的情况 func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { isPerformingModalReqest = false print("Authorization error: \(error.localizedDescription)") NotificationCenter.default.post(name: .ModalSignInSheetCanceled, object: nil) } // 实现presentationContextProvider的方法 func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return authenticationAnchor! } }
关键说明
- 异步处理:Passkey认证是异步操作,必须等待凭证获取完成后再调用
call.resolve,不能在presentViewController后立即执行。 - 闭包回调:通过给ViewController添加回调闭包,实现从ViewController到Plugin的通信,避免全局变量导致的内存泄漏。
- 数据序列化:把ASAuthorization的凭证对象转换为base64编码的字符串或字典,确保能传递到JS端(JS无法直接接收原生对象)。
- 内存管理:使用
weak self避免循环引用,在ViewController销毁时移除通知监听。
内容的提问来源于stack exchange,提问作者Charas
相关产品推荐
相关产品推荐

