diff --git a/.changes/mobile-plugin-get-args.md b/.changes/mobile-plugin-get-args.md new file mode 100644 index 000000000..2fb6dea4f --- /dev/null +++ b/.changes/mobile-plugin-get-args.md @@ -0,0 +1,6 @@ +--- +"tauri": patch:enhance +--- + +Added `getArgs` and `getRawArgs` methods to the plugin `Invoke` class (Kotlin and Swift), +which lets you parse the arguments manually instead of through the `parseArgs` method. diff --git a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt index d951ca55b..1f9d08007 100644 --- a/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt +++ b/core/tauri/mobile/android/src/main/java/app/tauri/plugin/Invoke.kt @@ -17,6 +17,14 @@ class Invoke( private val argsJson: String, private val jsonMapper: ObjectMapper ) { + fun getRawArgs(): String { + return argsJson + } + + fun getArgs(): JSObject { + return JSObject(argsJson) + } + fun parseArgs(cls: Class): T { return jsonMapper.readValue(argsJson, cls) } diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift b/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift index b1b5dbfe5..6f810868f 100644 --- a/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift +++ b/core/tauri/mobile/ios-api/Sources/Tauri/Invoke.swift @@ -26,6 +26,17 @@ import UIKit self.sendChannelData = sendChannelData } + public func getRawArgs() -> String { + return self.data + } + + public func getArgs() throws -> JSObject { + let jsonData = self.data.data(using: .utf8)! + let data = try JSONSerialization.jsonObject(with: jsonData, options: []) + return JSTypes.coerceDictionaryToJSObject( + (data as! NSDictionary), formattingDatesAsStrings: true)! + } + public func parseArgs(_ type: T.Type) throws -> T { let jsonData = self.data.data(using: .utf8)! let decoder = JSONDecoder() diff --git a/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift b/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift index 8fd5d2f29..0ac9f2c79 100644 --- a/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift +++ b/core/tauri/mobile/ios-api/Sources/Tauri/JSTypes.swift @@ -20,3 +20,104 @@ extension Dictionary: JSValue where Key == String, Value == JSValue {} // convenience aliases public typealias JSObject = [String: JSValue] public typealias JSArray = [JSValue] + +extension Dictionary where Key == String, Value == JSValue { + public func getValue(_ key: String) -> JSValue? { + return self[key] + } + + public func getString(_ key: String) -> String? { + return self[key] as? String + } + + public func getBool(_ key: String) -> Bool? { + return self[key] as? Bool + } + + public func getInt(_ key: String) -> Int? { + return self[key] as? Int + } + + public func getFloat(_ key: String) -> Float? { + if let floatValue = self[key] as? Float { + return floatValue + } else if let doubleValue = self[key] as? Double { + return Float(doubleValue) + } + return nil + } + + public func getDouble(_ key: String) -> Double? { + return self[key] as? Double + } + + public func getArray(_ key: String) -> JSArray? { + return self[key] as? JSArray + } + + public func getObject(_ key: String) -> JSObject? { + return self[key] as? JSObject + } +} + +/* + Simply casting objects from foundation class clusters (such as __NSArrayM) + doesn't work with the JSValue protocol and will always fail. So we need to + recursively and explicitly convert each value in the dictionary. + */ +public enum JSTypes {} +extension JSTypes { + public static func coerceDictionaryToJSObject( + _ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceDictionaryToJSObject( + _ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } +} + +private let dateStringFormatter = ISO8601DateFormatter() + +// We need a large switch statement because we have a lot of types. +// swiftlint:disable:next cyclomatic_complexity +private func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let numberValue as NSNumber: + return numberValue + case let boolValue as Bool: + return boolValue + case let intValue as Int: + return intValue + case let floatValue as Float: + return floatValue + case let doubleValue as Double: + return doubleValue + case let dateValue as Date: + if formattingDates { + return dateStringFormatter.string(from: dateValue) + } + return dateValue + case let nullValue as NSNull: + return nullValue + case let arrayValue as NSArray: + return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) } + case let dictionaryValue as NSDictionary: + let keys = dictionaryValue.allKeys.compactMap { $0 as? String } + var result: JSObject = [:] + for key in keys { + result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates) + } + return result + default: + return nil + } +}