如何从iOS App向Safari扩展发送认证令牌?
iOS App与Safari扩展同步登录状态解决方案
需求背景
用户在iOS App登录后,通过共享UserDefaults存储认证令牌(Access-Token、Client、Uid),代码如下:
class AuthDataService { {...} URLSession.shared.dataTaskPublisher(for: urlRequest) .tryMap { data, response -> Data in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let accessToken = httpResponse.value(forHTTPHeaderField: "Access-Token"), let clientId = httpResponse.value(forHTTPHeaderField: "Client"), let uid = httpResponse.value(forHTTPHeaderField: "Uid") else { throw CustomError.cannotExecuteRequest } let sharedDefaults = UserDefaults(suiteName: "group.com.MyCompany.MyProject") sharedDefaults?.set(accessToken, forKey: "Access-Token") sharedDefaults?.set(clientId, forKey: "Client") sharedDefaults?.set(uid, forKey: "Uid") return data } {...} }
已创建同名App Group实现数据共享,用户登录后通过App内链接跳转至Safari:
struct HomeView: View { @EnvironmentObject var viewModel: AuthViewModel var body: some View { Link(destination: URL(string: "https://www.apple.com/")!) { Text("Take me to Safari") } } }
遇到的问题
尝试在SafariWebExtensionHandler中读取共享令牌,但readData方法未执行,且iOS不支持SFSafariApplication.dispatchMessage主动向扩展发消息:
import SafariServices import os.log let SFExtensionMessageKey = "message" class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { func readData() { let sharedDefaults = UserDefaults(suiteName: "group.com.lever.clientTokens") // 此处App Group名称与实际不符 print(sharedDefaults?.object(forKey: "Access-Token")) // 未执行 } func beginRequest(with context: NSExtensionContext) { let item = context.inputItems[0] as! NSExtensionItem let message = item.userInfo?[SFExtensionMessageKey] os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg) let response = NSExtensionItem() response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ] readData() context.completeRequest(returningItems: [response], completionHandler: nil) } }
核心问题解答
1. iOS中通过SafariWebExtensionHandler实现App向扩展主动传输数据是否可行?
不可行。iOS的SafariWebExtensionHandler仅在扩展的JavaScript端通过browser.runtime.sendNativeMessage主动发起请求时才会被唤醒执行,App无法主动触发该Handler的方法。因此,App到扩展的数据流只能通过「App写入共享存储,扩展主动读取」的方式实现。
2. 如何实现跳转Safari后自动同步登录状态?
通过「共享存储+扩展主动请求」的方案实现,具体步骤如下:
步骤1:修正App Group名称一致性
首先修复SafariWebExtensionHandler中的App Group名称错误,确保与实际创建的group.com.MyCompany.MyProject一致:
func getAuthTokens() -> [String: Any]? { let sharedDefaults = UserDefaults(suiteName: "group.com.MyCompany.MyProject") return sharedDefaults?.dictionaryWithValues(forKeys: ["Access-Token", "Client", "Uid"]) }
步骤2:改造Handler处理扩展的请求
修改beginRequest(with:)方法,根据扩展JS发送的指令返回令牌数据:
func beginRequest(with context: NSExtensionContext) { let item = context.inputItems[0] as! NSExtensionItem guard let message = item.userInfo?[SFExtensionMessageKey] as? [String: Any], let action = message["action"] as? String else { context.completeRequest(returningItems: [], completionHandler: nil) return } var responseData = [String: Any]() if action == "fetchAuthTokens" { responseData["tokens"] = getAuthTokens() } let responseItem = NSExtensionItem() responseItem.userInfo = [SFExtensionMessageKey: responseData] context.completeRequest(returningItems: [responseItem], completionHandler: nil) }
步骤3:扩展JS主动请求令牌
在扩展的background.js或内容脚本中,当页面加载时(或检测到来自App的跳转参数时),向原生Handler发送请求获取令牌:
// 示例:background.js browser.runtime.onInstalled.addListener(() => { // 初始化时请求一次令牌 fetchTokens(); }); // 监听标签页加载,检测来自App的跳转 browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === "complete" && tab.url.includes("fromApp=1")) { fetchTokens(); } }); function fetchTokens() { browser.runtime.sendNativeMessage( "com.MyCompany.MyProject.SafariWebExtension", // 替换为你的扩展Bundle ID { action: "fetchAuthTokens" }, (response) => { if (response?.tokens) { // 将令牌存入扩展本地存储,供后续请求使用 localStorage.setItem("accessToken", response.tokens["Access-Token"]); localStorage.setItem("client", response.tokens.Client); localStorage.setItem("uid", response.tokens.Uid); } } ); }
步骤4:优化跳转逻辑(可选)
在App跳转Safari时,添加URL参数标记来源,让扩展仅在从App跳转时同步令牌,避免不必要的请求:
Link(destination: URL(string: "https://www.apple.com/?fromApp=1")!) { Text("Take me to Safari") }
内容的提问来源于stack exchange,提问作者Nico Cobelo
相关产品推荐
相关产品推荐

