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

Nic*_*elo 8 safari-extension ios swift

我尝试在诸如此和此之类的帖子中寻找解决方案,其中人们遇到了非常相似的问题:How to send a message from iOS App to a Safari Extension?

\n

我什至阅读了这篇文章,其中作者解释了如何使用SafariExtensionHandler从浏览器向应用程序发送消息,并在选择上下文菜单后返回浏览器,但这并不完全是我想要的。

\n

从 iOS 应用程序向 Safari 扩展发送令牌

\n

在应用程序中,用户必须输入电子邮件和密码才能登录其帐户。一旦他们登录,我就会UserDefaults像这样保存他们的信息:

\n
class AuthDataService {\n{...}\n        URLSession.shared.dataTaskPublisher(for: urlRequest)\n            .tryMap { data, response -> Data in\n                guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200,\n                      let accessToken = httpResponse.value(forHTTPHeaderField: "Access-Token"),\n                      let clientId = httpResponse.value(forHTTPHeaderField: "Client"),\n                      let uid = httpResponse.value(forHTTPHeaderField: "Uid")\n                else {\n                    throw CustomError.cannotExecuteRequest\n                }\n                \n                let sharedDefaults = UserDefaults(suiteName: "group.com.MyCompany.MyProject")\n                sharedDefaults?.set(accessToken, forKey: "Access-Token")\n                sharedDefaults?.set(clientId, forKey: "Client")\n                sharedDefaults?.set(uid, forKey: "Uid")\n\n                return data\n            }\n{...}\n}\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序组

\n

根据我对本文的理解,我需要创建一个应用程序组,以便在 iOS 应用程序和 Safari 扩展之间共享数据。我将该组命名为:("group.com.MyCompany.MyProject"就像 中的 suiteName 一样UserDefaults)。

\n

主页视图

\n

用户登录时看到的屏幕是一个 SwiftUI 视图,其中有一个链接,可将用户带到 Safari,以便他们可以自己打开扩展:

\n
struct HomeView: View {\n    @EnvironmentObject var viewModel: AuthViewModel\n    var body: some View {\n        Link(destination: URL(string: "https://www.apple.com/")!) {\n            Text("Take me to Safari")\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

SafariWebExtensionHandler

\n

现在,我读到的所有文章都在讨论如何通过 SafariWebExtensionHandler 将数据从 Safari 扩展发送到 iOS 应用程序beginRequest(with:)。\n但是,我尝试在用户登录时发送 UserDefaults 中的令牌。应用程序,或者当他们打开 Safari 扩展时。

\n

我尝试从中检索数据,UserDefaults看看是否至少可以在终端中读取它,但调试器永远不会到达print:

\n
import SafariServices\nimport os.log\n\nlet SFExtensionMessageKey = "message"\n\nclass SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {\n\n    func readData() {\n        let sharedDefaults = UserDefaults(suiteName: "group.com.lever.clientTokens")\n        print(sharedDefaults?.object(forKey: "Access-Token")) //<-- This line never gets executed\n    }\n    \n    func beginRequest(with context: NSExtensionContext) {\n        let item = context.inputItems[0] as! NSExtensionItem\n        let message = item.userInfo?[SFExtensionMessageKey]\n        os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)\n\n        let response = NSExtensionItem()\n        response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]\n\n        readData()\n        \n        context.completeRequest(returningItems: [response], completionHandler: nil)\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

问题

\n

macOS 与 iOS

\n

Apple 的这份文档中有一个名为Send messages from the app to JavaScript“这正是我想要做的”的部分。文档甚至提到SFSafariApplication.dispatchMessage(withName:toExtensionWithIdentifier:userInfo:completionHandler:)理论上它会向 JavaScript 脚本发送一条消息,但它说它仅适用于 macOS:

\n
\n

您可以\xe2\x80\x99t 将消息从包含的 iOS 应用程序发送到您的 Web\nextension\xe2\x80\x99s JavaScript 脚本。

\n
\n

这篇优秀的 Medium 文章讨论了使用 openai.com 的 API 将 APIKey 从应用程序发送到 Safari 扩展。看来它也是用来SFSafariApplication沟通的SafariWebExtensionHandler,但它看起来又只适用于 macOS。

\n

Safari 网页扩展

\n

我还阅读了其他 Apple 文档认为它会有所帮助,但它只讨论了将消息从 Safari 扩展的弹出窗口传递到网页。

\n

结论

\n

所以我的问题是:

\n

是否以正确的方式编写代码SafariWebExtensionHandler将数据从 iOS 应用程序发送到我的 Safari 扩展?这可以在 iOS 中完成吗?还是仅适用于 macOS?

\n

我读过一些其他文章,讨论如何使用 Resources 文件夹中的 JavaScript 文件来“监听”更改。但我有点困惑如何从我的应用程序发送这些更改以便 Safari 扩展能够监听它们。

\n

我想要实现的是,用户从 iOS 应用程序中的 HomeView 重定向后已经登录到 Safari 扩展中,而不必再次登录。

\n

感谢您的时间和帮助!

\n

Blu*_*ick 0

虽然应用程序无法发起与分机的连接,但支持另一个方向。当扩展程序将数据发送到应用程序时,您可以附加响应并将其发送回扩展程序。

我不确定为什么你的使用示例SafariWebExtensionHandler不起作用。看起来不错。也许问题出在 JS 方面。

应用程序

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

扩大

首先添加"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)
  }
}
Run Code Online (Sandbox Code Playgroud)