You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:35:29