Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
158 lines
4.8 KiB
Swift
158 lines
4.8 KiB
Swift
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import Foundation
|
|
import SwiftRs
|
|
import UIKit
|
|
import WebKit
|
|
import os.log
|
|
|
|
class PluginHandle {
|
|
var instance: Plugin
|
|
var loaded = false
|
|
|
|
init(plugin: Plugin) {
|
|
instance = plugin
|
|
}
|
|
}
|
|
|
|
public class PluginManager {
|
|
static let shared: PluginManager = PluginManager()
|
|
public var viewController: UIViewController?
|
|
var plugins: [String: PluginHandle] = [:]
|
|
var ipcDispatchQueue = DispatchQueue(label: "ipc")
|
|
public var isSimEnvironment: Bool {
|
|
#if targetEnvironment(simulator)
|
|
return true
|
|
#else
|
|
return false
|
|
#endif
|
|
}
|
|
|
|
public func assetUrl(fromLocalURL url: URL?) -> URL? {
|
|
guard let inputURL = url else {
|
|
return nil
|
|
}
|
|
|
|
return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path)
|
|
}
|
|
|
|
func onWebviewCreated(_ webview: WKWebView) {
|
|
for (_, handle) in plugins {
|
|
if !handle.loaded {
|
|
handle.instance.load(webview: webview)
|
|
}
|
|
}
|
|
}
|
|
|
|
func load<P: Plugin>(name: String, plugin: P, config: String, webview: WKWebView?) {
|
|
plugin.setConfig(config)
|
|
let handle = PluginHandle(plugin: plugin)
|
|
if let webview = webview {
|
|
handle.instance.load(webview: webview)
|
|
handle.loaded = true
|
|
}
|
|
plugins[name] = handle
|
|
}
|
|
|
|
func invoke(name: String, invoke: Invoke) {
|
|
if let plugin = plugins[name] {
|
|
ipcDispatchQueue.async {
|
|
let selectorWithCompletionHandler = Selector(("\(invoke.command):completionHandler:"))
|
|
let selectorWithThrows = Selector(("\(invoke.command):error:"))
|
|
|
|
if plugin.instance.responds(to: selectorWithCompletionHandler) {
|
|
let completion: @convention(block) (NSError?) -> Void = { error in
|
|
if let error = error {
|
|
invoke.reject("\(error)")
|
|
}
|
|
}
|
|
|
|
let blockObj: AnyObject = unsafeBitCast(completion, to: AnyObject.self)
|
|
let imp = plugin.instance.method(for: selectorWithCompletionHandler)
|
|
|
|
typealias Fn = @convention(c) (AnyObject, Selector, Invoke, AnyObject) -> Void
|
|
let fn = unsafeBitCast(imp, to: Fn.self)
|
|
fn(plugin.instance, selectorWithCompletionHandler, invoke, blockObj)
|
|
} else if plugin.instance.responds(to: selectorWithThrows) {
|
|
var error: NSError? = nil
|
|
withUnsafeMutablePointer(to: &error) {
|
|
let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows)
|
|
unsafeBitCast(
|
|
methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)(
|
|
plugin.instance, selectorWithThrows, invoke, OpaquePointer($0))
|
|
}
|
|
if let error = error {
|
|
invoke.reject("\(error)")
|
|
// TODO: app crashes without this leak
|
|
let _ = Unmanaged.passRetained(error)
|
|
}
|
|
} else {
|
|
let selector = Selector(("\(invoke.command):"))
|
|
if plugin.instance.responds(to: selector) {
|
|
plugin.instance.perform(selector, with: invoke)
|
|
} else {
|
|
invoke.reject("No command \(invoke.command) found for plugin \(name)")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
invoke.reject("Plugin \(name) not initialized")
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PluginManager: NSCopying {
|
|
public func copy(with zone: NSZone? = nil) -> Any {
|
|
return self
|
|
}
|
|
}
|
|
|
|
private var stdoutRedirector: StdoutRedirector?
|
|
|
|
@_cdecl("log_stdout")
|
|
func logStdout() {
|
|
stdoutRedirector = StdoutRedirector()
|
|
stdoutRedirector!.start()
|
|
}
|
|
|
|
@_cdecl("register_plugin")
|
|
func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) {
|
|
PluginManager.shared.load(
|
|
name: name.toString(),
|
|
plugin: plugin as! Plugin,
|
|
config: config.toString(),
|
|
webview: webview
|
|
)
|
|
}
|
|
|
|
@_cdecl("on_webview_created")
|
|
func onWebviewCreated(webview: WKWebView, viewController: UIViewController) {
|
|
PluginManager.shared.viewController = viewController
|
|
PluginManager.shared.onWebviewCreated(webview)
|
|
}
|
|
|
|
@_cdecl("run_plugin_command")
|
|
func runCommand(
|
|
id: Int,
|
|
name: SRString,
|
|
command: SRString,
|
|
data: SRString,
|
|
callback: @escaping @convention(c) (Int, Bool, UnsafePointer<CChar>) -> Void,
|
|
sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer<CChar>) -> Void
|
|
) {
|
|
let callbackId: UInt64 = 0
|
|
let errorId: UInt64 = 1
|
|
let invoke = Invoke(
|
|
command: command.toString(), callback: callbackId, error: errorId,
|
|
sendResponse: { (fn: UInt64, payload: String?) -> Void in
|
|
let success = fn == callbackId
|
|
callback(id, success, payload ?? "null")
|
|
},
|
|
sendChannelData: { (id: UInt64, payload: String) -> Void in
|
|
sendChannelData(id, payload)
|
|
}, data: data.toString())
|
|
PluginManager.shared.invoke(name: name.toString(), invoke: invoke)
|
|
}
|