/
Tauri.swift
145 lines (131 loc) · 4.56 KB
/
Tauri.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import SwiftRs
import Foundation
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: JSObject, 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 selectorWithThrows = Selector(("\(invoke.command):error:"))
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
}
}
@_cdecl("register_plugin")
func registerPlugin(name: SRString, plugin: NSObject, config: NSDictionary?, webview: WKWebView?) {
PluginManager.shared.load(
name: name.toString(),
plugin: plugin as! Plugin,
config: JSTypes.coerceDictionaryToJSObject(config ?? [:], formattingDatesAsStrings: true)!,
webview: webview
)
}
@_cdecl("on_webview_created")
func onWebviewCreated(webview: WKWebView, viewController: UIViewController) {
PluginManager.shared.viewController = viewController
PluginManager.shared.onWebviewCreated(webview)
}
@_cdecl("post_ipc_message")
func postIpcMessage(webview: WKWebView, name: SRString, command: SRString, data: NSDictionary, callback: UInt, error: UInt) {
let invoke = Invoke(command: command.toString(), sendResponse: { (successResult: JsonValue?, errorResult: JsonValue?) -> Void in
let (fn, payload) = errorResult == nil ? (callback, successResult) : (error, errorResult)
var payloadJson: String
do {
try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`"
} catch {
payloadJson = "`\(error)`"
}
webview.evaluateJavaScript("window['_\(fn)'](\(payloadJson))")
}, data: JSTypes.coerceDictionaryToJSObject(data, formattingDatesAsStrings: true))
PluginManager.shared.invoke(name: name.toString(), invoke: invoke)
}
@_cdecl("run_plugin_method")
func runCommand(
id: Int,
name: SRString,
command: SRString,
data: NSDictionary,
callback: @escaping @convention(c) (Int, Bool, UnsafePointer<CChar>?) -> Void
) {
let invoke = Invoke(command: command.toString(), sendResponse: { (successResult: JsonValue?, errorResult: JsonValue?) -> Void in
let (success, payload) = errorResult == nil ? (true, successResult) : (false, errorResult)
var payloadJson: String = ""
do {
try payloadJson = payload == nil ? "null" : payload!.jsonRepresentation() ?? "`Failed to serialize payload`"
} catch {
payloadJson = "`\(error)`"
}
callback(id, success, payloadJson.cString(using: String.Encoding.utf8))
}, data: JSTypes.coerceDictionaryToJSObject(data, formattingDatesAsStrings: true))
PluginManager.shared.invoke(name: name.toString(), invoke: invoke)
}