fix: explicitly set a minimum macOS version for older Swift versions (#1679)
ref https://github.com/tauri-apps/tauri/pull/10687pull/1680/head
parent
66b9eaa0e5
commit
b914775898
@ -0,0 +1,15 @@
|
||||
---
|
||||
"barcode-scanner": patch
|
||||
"biometric": patch
|
||||
"clipboard-manager": patch
|
||||
"dialog": patch
|
||||
"geolocation": patch
|
||||
"haptics": patch
|
||||
"log-plugin": patch
|
||||
"nfc": patch
|
||||
"notification": patch
|
||||
"shell": patch
|
||||
"store": patch
|
||||
---
|
||||
|
||||
Explicitly set a minimum macOS version for the Swift package.
|
@ -1,10 +0,0 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
Package.resolved
|
@ -1,40 +0,0 @@
|
||||
// swift-tools-version:5.3
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Tauri",
|
||||
platforms: [
|
||||
.macOS(.v10_13),
|
||||
.iOS(.v11),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "Tauri",
|
||||
type: .static,
|
||||
targets: ["Tauri"])
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(name: "SwiftRs", url: "https://github.com/Brendonovich/swift-rs", from: "1.0.0")
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "Tauri",
|
||||
dependencies: [
|
||||
.byName(name: "SwiftRs")
|
||||
],
|
||||
path: "Sources"
|
||||
),
|
||||
.testTarget(
|
||||
name: "TauriTests",
|
||||
dependencies: ["Tauri"]
|
||||
),
|
||||
]
|
||||
)
|
@ -1,3 +0,0 @@
|
||||
# Tauri
|
||||
|
||||
Tauri iOS API.
|
@ -1,65 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import Foundation
|
||||
|
||||
let CHANNEL_PREFIX = "__CHANNEL__:"
|
||||
let channelDataKey = CodingUserInfoKey(rawValue: "sendChannelData")!
|
||||
|
||||
public class Channel: Decodable {
|
||||
public let id: UInt64
|
||||
let handler: (UInt64, String) -> Void
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let channelDef = try container.decode(String.self)
|
||||
|
||||
let components = channelDef.components(separatedBy: CHANNEL_PREFIX)
|
||||
if components.count < 2 {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Invalid channel definition from \(channelDef)"
|
||||
)
|
||||
|
||||
}
|
||||
guard let channelId = UInt64(components[1]) else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Invalid channel ID from \(channelDef)"
|
||||
)
|
||||
}
|
||||
|
||||
guard let handler = decoder.userInfo[channelDataKey] as? (UInt64, String) -> Void else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "missing userInfo for Channel handler. This is a Tauri issue"
|
||||
)
|
||||
}
|
||||
|
||||
self.id = channelId
|
||||
self.handler = handler
|
||||
}
|
||||
|
||||
func serialize(_ data: JsonValue) -> String {
|
||||
do {
|
||||
return try data.jsonRepresentation() ?? "\"Failed to serialize payload\""
|
||||
} catch {
|
||||
return "\"\(error)\""
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ data: JsonObject) {
|
||||
send(.dictionary(data))
|
||||
}
|
||||
|
||||
public func send(_ data: JsonValue) {
|
||||
handler(id, serialize(data))
|
||||
}
|
||||
|
||||
public func send<T: Encodable>(_ data: T) throws {
|
||||
let json = try JSONEncoder().encode(data)
|
||||
handler(id, String(decoding: json, as: UTF8.self))
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objc public class Invoke: NSObject {
|
||||
public let command: String
|
||||
let callback: UInt64
|
||||
let error: UInt64
|
||||
let data: String
|
||||
let sendResponse: (UInt64, String?) -> Void
|
||||
let sendChannelData: (UInt64, String) -> Void
|
||||
|
||||
public init(
|
||||
command: String, callback: UInt64, error: UInt64,
|
||||
sendResponse: @escaping (UInt64, String?) -> Void,
|
||||
sendChannelData: @escaping (UInt64, String) -> Void, data: String
|
||||
) {
|
||||
self.command = command
|
||||
self.callback = callback
|
||||
self.error = error
|
||||
self.data = data
|
||||
self.sendResponse = sendResponse
|
||||
self.sendChannelData = sendChannelData
|
||||
}
|
||||
|
||||
public func parseArgs<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
let jsonData = self.data.data(using: .utf8)!
|
||||
let decoder = JSONDecoder()
|
||||
decoder.userInfo[channelDataKey] = sendChannelData
|
||||
return try decoder.decode(type, from: jsonData)
|
||||
}
|
||||
|
||||
func serialize(_ data: JsonValue) -> String {
|
||||
do {
|
||||
return try data.jsonRepresentation() ?? "\"Failed to serialize payload\""
|
||||
} catch {
|
||||
return "\"\(error)\""
|
||||
}
|
||||
}
|
||||
|
||||
public func resolve() {
|
||||
sendResponse(callback, nil)
|
||||
}
|
||||
|
||||
public func resolve(_ data: JsonObject) {
|
||||
resolve(.dictionary(data))
|
||||
}
|
||||
|
||||
public func resolve(_ data: JsonValue) {
|
||||
sendResponse(callback, serialize(data))
|
||||
}
|
||||
|
||||
public func resolve<T: Encodable>(_ data: T) {
|
||||
do {
|
||||
let json = try JSONEncoder().encode(data)
|
||||
sendResponse(callback, String(decoding: json, as: UTF8.self))
|
||||
} catch {
|
||||
sendResponse(self.error, "\"\(error)\"")
|
||||
}
|
||||
}
|
||||
|
||||
public func reject(
|
||||
_ message: String, code: String? = nil, error: Error? = nil, data: JsonValue? = nil
|
||||
) {
|
||||
let payload: NSMutableDictionary = [
|
||||
"message": message
|
||||
]
|
||||
|
||||
if let code = code {
|
||||
payload["code"] = code
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
payload["error"] = error
|
||||
}
|
||||
|
||||
if let data = data {
|
||||
switch data {
|
||||
case .dictionary(let dict):
|
||||
for entry in dict {
|
||||
payload[entry.key] = entry.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendResponse(self.error, serialize(.dictionary(payload as! JsonObject)))
|
||||
}
|
||||
|
||||
public func unimplemented() {
|
||||
unimplemented("not implemented")
|
||||
}
|
||||
|
||||
public func unimplemented(_ message: String) {
|
||||
reject(message)
|
||||
}
|
||||
|
||||
public func unavailable() {
|
||||
unavailable("not available")
|
||||
}
|
||||
|
||||
public func unavailable(_ message: String) {
|
||||
reject(message)
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import Foundation
|
||||
|
||||
// declare our empty protocol, and conformance, for typing
|
||||
public protocol JSValue {}
|
||||
extension String: JSValue {}
|
||||
extension Bool: JSValue {}
|
||||
extension Int: JSValue {}
|
||||
extension Float: JSValue {}
|
||||
extension Double: JSValue {}
|
||||
extension NSNumber: JSValue {}
|
||||
extension NSNull: JSValue {}
|
||||
extension Array: JSValue {}
|
||||
extension Date: JSValue {}
|
||||
extension Dictionary: JSValue where Key == String, Value == JSValue {}
|
||||
|
||||
// convenience aliases
|
||||
public typealias JSObject = [String: JSValue]
|
||||
public typealias JSArray = [JSValue]
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import Foundation
|
||||
|
||||
public typealias JsonObject = [String: Any]
|
||||
|
||||
public enum JsonValue {
|
||||
case dictionary(JsonObject)
|
||||
|
||||
enum SerializationError: Error {
|
||||
case invalidObject
|
||||
}
|
||||
|
||||
public func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? {
|
||||
switch self {
|
||||
case .dictionary(var dictionary):
|
||||
if let fields = includingFields {
|
||||
dictionary.merge(fields) { (current, _) in current }
|
||||
}
|
||||
dictionary = prepare(dictionary: dictionary)
|
||||
guard JSONSerialization.isValidJSONObject(dictionary) else {
|
||||
throw SerializationError.invalidObject
|
||||
}
|
||||
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
private static let formatter = ISO8601DateFormatter()
|
||||
|
||||
private func prepare(dictionary: JsonObject) -> JsonObject {
|
||||
return dictionary.mapValues { (value) -> Any in
|
||||
if let date = value as? Date {
|
||||
return JsonValue.formatter.string(from: date)
|
||||
} else if let aDictionary = value as? JsonObject {
|
||||
return prepare(dictionary: aDictionary)
|
||||
} else if let anArray = value as? [Any] {
|
||||
return prepare(array: anArray)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
private func prepare(array: [Any]) -> [Any] {
|
||||
return array.map { (value) -> Any in
|
||||
if let date = value as? Date {
|
||||
return JsonValue.formatter.string(from: date)
|
||||
} else if let aDictionary = value as? JsonObject {
|
||||
return prepare(dictionary: aDictionary)
|
||||
} else if let anArray = value as? [Any] {
|
||||
return prepare(array: anArray)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
|
||||
/// Wrapper class for os_log function
|
||||
public class Logger {
|
||||
private static var _enabled = false
|
||||
public static var enabled: Bool {
|
||||
get {
|
||||
#if DEBUG
|
||||
return true
|
||||
#else
|
||||
return _enabled
|
||||
#endif
|
||||
}
|
||||
set {
|
||||
Logger._enabled = newValue
|
||||
}
|
||||
}
|
||||
|
||||
static func log(_ items: Any..., category: String, type: OSLogType) {
|
||||
if Logger.enabled {
|
||||
var message = ""
|
||||
let last = items.count - 1
|
||||
for (index, item) in items.enumerated() {
|
||||
message += "\(item)"
|
||||
if index != last {
|
||||
message += " "
|
||||
}
|
||||
}
|
||||
let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "-", category: category)
|
||||
os_log("%{public}@", log: log, type: type, String(message.prefix(4068)))
|
||||
}
|
||||
}
|
||||
|
||||
public static func debug(_ items: Any..., category: String = "app") {
|
||||
#if DEBUG
|
||||
Logger.log(items, category: category, type: OSLogType.default)
|
||||
#else
|
||||
Logger.log(items, category: category, type: OSLogType.debug)
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func info(_ items: Any..., category: String = "app") {
|
||||
#if DEBUG
|
||||
Logger.log(items, category: category, type: OSLogType.default)
|
||||
#else
|
||||
Logger.log(items, category: category, type: OSLogType.info)
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func error(_ items: Any..., category: String = "app") {
|
||||
Logger.log(items, category: category, type: OSLogType.error)
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import WebKit
|
||||
import os.log
|
||||
|
||||
struct RegisterListenerArgs: Decodable {
|
||||
let event: String
|
||||
let handler: Channel
|
||||
}
|
||||
|
||||
struct RemoveListenerArgs: Decodable {
|
||||
let event: String
|
||||
let channelId: UInt64
|
||||
}
|
||||
|
||||
open class Plugin: NSObject {
|
||||
public let manager: PluginManager = PluginManager.shared
|
||||
var config: String = "{}"
|
||||
private var listeners = [String: [Channel]]()
|
||||
|
||||
internal func setConfig(_ config: String) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
public func parseConfig<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
let jsonData = self.config.data(using: .utf8)!
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(type, from: jsonData)
|
||||
}
|
||||
|
||||
@objc open func load(webview: WKWebView) {}
|
||||
|
||||
@objc open func checkPermissions(_ invoke: Invoke) {
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc open func requestPermissions(_ invoke: Invoke) {
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
public func trigger(_ event: String, data: JSObject) {
|
||||
if let eventListeners = listeners[event] {
|
||||
for channel in eventListeners {
|
||||
channel.send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func trigger<T: Encodable>(_ event: String, data: T) throws {
|
||||
if let eventListeners = listeners[event] {
|
||||
for channel in eventListeners {
|
||||
try channel.send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func registerListener(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(RegisterListenerArgs.self)
|
||||
|
||||
if var eventListeners = listeners[args.event] {
|
||||
eventListeners.append(args.handler)
|
||||
} else {
|
||||
listeners[args.event] = [args.handler]
|
||||
}
|
||||
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func removeListener(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(RemoveListenerArgs.self)
|
||||
|
||||
if let eventListeners = listeners[args.event] {
|
||||
|
||||
listeners[args.event] = eventListeners.filter { $0.id != args.channelId }
|
||||
}
|
||||
|
||||
invoke.resolve()
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
// 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 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: 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)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import UIKit
|
||||
|
||||
public class UIUtils {
|
||||
public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) {
|
||||
if let viewController = rootViewController {
|
||||
popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0)
|
||||
popoverController.popoverPresentationController?.sourceView = viewController.view
|
||||
popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue