Tauri.swift 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. import Foundation
  5. import SwiftRs
  6. import UIKit
  7. import WebKit
  8. import os.log
  9. class PluginHandle {
  10. var instance: Plugin
  11. var loaded = false
  12. init(plugin: Plugin) {
  13. instance = plugin
  14. }
  15. }
  16. public class PluginManager {
  17. static let shared: PluginManager = PluginManager()
  18. public var viewController: UIViewController?
  19. var plugins: [String: PluginHandle] = [:]
  20. var ipcDispatchQueue = DispatchQueue(label: "ipc")
  21. public var isSimEnvironment: Bool {
  22. #if targetEnvironment(simulator)
  23. return true
  24. #else
  25. return false
  26. #endif
  27. }
  28. public func assetUrl(fromLocalURL url: URL?) -> URL? {
  29. guard let inputURL = url else {
  30. return nil
  31. }
  32. return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path)
  33. }
  34. func onWebviewCreated(_ webview: WKWebView) {
  35. for (_, handle) in plugins {
  36. if !handle.loaded {
  37. handle.instance.load(webview: webview)
  38. }
  39. }
  40. }
  41. func load<P: Plugin>(name: String, plugin: P, config: String, webview: WKWebView?) {
  42. plugin.setConfig(config)
  43. let handle = PluginHandle(plugin: plugin)
  44. if let webview = webview {
  45. handle.instance.load(webview: webview)
  46. handle.loaded = true
  47. }
  48. plugins[name] = handle
  49. }
  50. func invoke(name: String, invoke: Invoke) {
  51. if let plugin = plugins[name] {
  52. ipcDispatchQueue.async {
  53. let selectorWithThrows = Selector(("\(invoke.command):error:"))
  54. if plugin.instance.responds(to: selectorWithThrows) {
  55. var error: NSError? = nil
  56. withUnsafeMutablePointer(to: &error) {
  57. let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows)
  58. unsafeBitCast(
  59. methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)(
  60. plugin.instance, selectorWithThrows, invoke, OpaquePointer($0))
  61. }
  62. if let error = error {
  63. invoke.reject("\(error)")
  64. // TODO: app crashes without this leak
  65. let _ = Unmanaged.passRetained(error)
  66. }
  67. } else {
  68. let selector = Selector(("\(invoke.command):"))
  69. if plugin.instance.responds(to: selector) {
  70. plugin.instance.perform(selector, with: invoke)
  71. } else {
  72. invoke.reject("No command \(invoke.command) found for plugin \(name)")
  73. }
  74. }
  75. }
  76. } else {
  77. invoke.reject("Plugin \(name) not initialized")
  78. }
  79. }
  80. }
  81. extension PluginManager: NSCopying {
  82. public func copy(with zone: NSZone? = nil) -> Any {
  83. return self
  84. }
  85. }
  86. @_cdecl("register_plugin")
  87. func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) {
  88. PluginManager.shared.load(
  89. name: name.toString(),
  90. plugin: plugin as! Plugin,
  91. config: config.toString(),
  92. webview: webview
  93. )
  94. }
  95. @_cdecl("on_webview_created")
  96. func onWebviewCreated(webview: WKWebView, viewController: UIViewController) {
  97. PluginManager.shared.viewController = viewController
  98. PluginManager.shared.onWebviewCreated(webview)
  99. }
  100. @_cdecl("run_plugin_command")
  101. func runCommand(
  102. id: Int,
  103. name: SRString,
  104. command: SRString,
  105. data: SRString,
  106. callback: @escaping @convention(c) (Int, Bool, UnsafePointer<CChar>) -> Void,
  107. sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer<CChar>) -> Void
  108. ) {
  109. let callbackId: UInt64 = 0
  110. let errorId: UInt64 = 1
  111. let invoke = Invoke(
  112. command: command.toString(), callback: callbackId, error: errorId,
  113. sendResponse: { (fn: UInt64, payload: String?) -> Void in
  114. let success = fn == callbackId
  115. callback(id, success, payload ?? "null")
  116. },
  117. sendChannelData: { (id: UInt64, payload: String) -> Void in
  118. sendChannelData(id, payload)
  119. }, data: data.toString())
  120. PluginManager.shared.invoke(name: name.toString(), invoke: invoke)
  121. }