swift 如何从iOS应用程序向其Safari扩展发送消息(令牌)?

q8l4jmvw  于 2023-06-04  发布在  Swift
关注(0)|答案(1)|浏览(188)

我试着在thisthis等帖子中寻找解决方案,人们遇到了非常相似的问题:如何从iOS应用程序发送消息到Safari扩展?
我甚至读了this article,作者解释了如何使用SafariExtensionHandler从浏览器向应用程序发送消息,并在选择上下文菜单后返回浏览器,但这并不是我想要的。

iOS App向Safari扩展发送Token

在应用程序中,用户必须输入电子邮件和密码才能登录他们的帐户。一旦他们登录,我将他们的信息保存在UserDefaults中,如下所示:

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
            }
{...}
}

应用群组

根据我对of this article的理解,我需要创建一个App Group,以便在iOS App和Safari Extension之间共享数据。我给这个团体取名:"group.com.MyCompany.MyProject"(就像UserDefaults中的suiteName)。

主视图

用户在登录时看到的屏幕是一个SwiftUI视图,其中有一个链接将用户带到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

现在,我读到的所有文章都在谈论如何通过SafariWebExtensionHandler的beginRequest(with:)将数据从Safari扩展发送到iOS应用程序。但是,我尝试在用户登录应用程序或打开Safari扩展时发送UserDefaults中的令牌。
我试着从UserDefaults中检索数据,看看我是否至少可以在终端中读取它,但调试器从未到达print语句:

import SafariServices
import os.log

let SFExtensionMessageKey = "message"

class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {

    func readData() {
        let sharedDefaults = UserDefaults(suiteName: "group.com.lever.clientTokens")
        print(sharedDefaults?.object(forKey: "Access-Token")) //<-- This line never gets executed
    }
    
    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)
    }

}

提问

macOS vs iOS

This documentation from Apple有一个名为Send messages from the app to JavaScript的部分,这几乎是我想做的。文档甚至提到了SFSafariApplication.dispatchMessage(withName:toExtensionWithIdentifier:userInfo:completionHandler:),理论上它会向JavaScript脚本发送消息,但它说它只在macOS中工作:
您无法将消息从包含的iOS应用发送到您的Web扩展的JavaScript脚本。
This excellent Medium article谈到使用openai.com的API将APIKey从应用程序发送到Safari扩展。它似乎也使用SFSafariApplicationSafariWebExtensionHandler通信,但看起来它只适用于macOS。

网页Safari扩展

我也读了this other Apple documentation,认为它会有所帮助,但它只谈到从Safari扩展的弹出窗口传递消息到网页。

总结

所以我的问题是
SafariWebExtensionHandler中编写代码是将数据从iOS应用程序发送到Safari扩展的正确方法吗?在iOS中可以这样做吗?是否仅适用于macOS?
我读过一些其他的文章,它们讨论了如何使用Resources文件夹中的JavaScript文件来“监听”更改。但是我有点困惑,我如何从我的应用程序发送这些更改,以便Safari扩展来监听它们。

我试图实现的是,用户在iOS应用程序中从HomeView重定向后,已经在Safari扩展中登录,而不必在另一时间登录。

感谢您的时间和帮助!

yquaqz18

yquaqz181#

虽然应用程序无法启动与扩展的连接,但支持另一个方向。当扩展将数据发送到应用程序时,您可以附加响应并将其发送回扩展。
我不知道为什么你使用SafariWebExtensionHandler的例子不起作用。看起来不错问题可能出在JS方面。

App

import SafariServices
import os.log

extension String: Error {}

let SFExtensionMessageKey = "message"

class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
    func beginRequest(with context: NSExtensionContext) {
        let item = context.inputItems[0] as! NSExtensionItem
        let data = item.userInfo?[SFExtensionMessageKey] as AnyObject?
        guard let key = data?["key"] as? String else {
            return
        }
        let response: [String: Any]
        do {
            switch(key) {
            case "getToken":
                os_log(.default, "handling getToken")
                guard let sharedDefault = UserDefaults(suiteName: "group.com.lever.clientTokens") else {
                    throw "UserDefaults not found"
                }
                guard let tokenData = sharedDefault.data(forKey: "Access-Token") else {
                    throw "Access-Token not found"
                }
                guard let tokenString = String(data: tokenData, encoding: .utf8) else {
                    throw "tokenData could not be converted into string";
                }
                response = ["success": true, "resp": tokenString]
            default:
                throw "Invalid key"
            }
        } catch {
            os_log(.error, "Error: %s", String(reflecting: error))
            response = ["success": false, "resp": ["message": error.localizedDescription]]
        }
        let resp = NSExtensionItem()
        resp.userInfo = [SFExtensionMessageKey: response]
        context.completeRequest(returningItems: [resp], completionHandler: nil)
    }
}

扩展

首先将"nativeMessaging"添加到'manifest.json'中的"permissions"

async function getToken() {
  const resp = await browser.runtime.sendNativeMessage("_", { 
    key: "getToken"
  });
  if (resp.success) {
    return resp.resp
  } else {
    throw new Error(resp.message)
  }
}

相关问题