Type-safe JavaScript-to-native method invocation with structured parameter access and response handling.
Core class representing method calls from JavaScript with type-safe parameter access and structured response methods.
/**
* Represents a method call from JavaScript with parameter access and response handling
*/
@objc(CAPPluginCall)
public class CAPPluginCall: NSObject {
// MARK: - Core Properties
/** Unique identifier for this call */
public let callbackId: String
/** Name of the method being called */
public let methodName: String
/** Dictionary containing call parameters from JavaScript */
public let options: NSDictionary
/** Whether this call should be retained after completion (for callbacks/events) */
public var keepAlive: Bool
/** Whether this call has been saved to the bridge */
public var isSaved: Bool { get }
// MARK: - Response Methods
/**
* Resolve the call with no data
*/
public func resolve()
/**
* Resolve the call with response data
* @param data Response data to send to JavaScript
*/
public func resolve(_ data: PluginCallResultData?)
/**
* Reject the call with an error message
* @param message Error message
*/
public func reject(_ message: String)
/**
* Reject the call with message and error code
* @param message Error message
* @param code Error code identifier
*/
public func reject(_ message: String, _ code: String?)
/**
* Reject the call with full error details
* @param message Error message
* @param code Error code identifier
* @param error Underlying Error object
*/
public func reject(_ message: String, _ code: String?, _ error: Error?)
/**
* Reject the call with complete error information
* @param message Error message
* @param code Error code identifier
* @param error Underlying Error object
* @param data Additional error data
*/
public func reject(_ message: String, _ code: String?, _ error: Error?, _ data: PluginCallResultData?)
/**
* Reject with standard "not implemented" error
*/
public func unimplemented()
/**
* Reject with custom "not implemented" message
* @param message Custom not implemented message
*/
public func unimplemented(_ message: String)
/**
* Reject with standard "unavailable" error
*/
public func unavailable()
/**
* Reject with custom "unavailable" message
* @param message Custom unavailable message
*/
public func unavailable(_ message: String)
// MARK: - Type-Safe Parameter Access
/**
* Get string parameter
* @param key Parameter name
* @returns String value or nil if not found
*/
public func getString(_ key: String) -> String?
/**
* Get string parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns String value or default
*/
public func getString(_ key: String, _ defaultValue: String?) -> String?
/**
* Get boolean parameter
* @param key Parameter name
* @returns Boolean value or false if not found/invalid
*/
public func getBool(_ key: String) -> Bool
/**
* Get boolean parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns Boolean value or default
*/
public func getBool(_ key: String, _ defaultValue: Bool) -> Bool
/**
* Get integer parameter
* @param key Parameter name
* @returns Integer value or 0 if not found/invalid
*/
public func getInt(_ key: String) -> Int
/**
* Get integer parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns Integer value or default
*/
public func getInt(_ key: String, _ defaultValue: Int) -> Int
/**
* Get float parameter
* @param key Parameter name
* @returns Float value or 0.0 if not found/invalid
*/
public func getFloat(_ key: String) -> Float
/**
* Get float parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns Float value or default
*/
public func getFloat(_ key: String, _ defaultValue: Float) -> Float
/**
* Get double parameter
* @param key Parameter name
* @returns Double value or 0.0 if not found/invalid
*/
public func getDouble(_ key: String) -> Double
/**
* Get double parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns Double value or default
*/
public func getDouble(_ key: String, _ defaultValue: Double) -> Double
/**
* Get object parameter
* @param key Parameter name
* @returns JSObject dictionary or nil if not found
*/
public func getObject(_ key: String) -> JSObject?
/**
* Get object parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns JSObject dictionary or default
*/
public func getObject(_ key: String, _ defaultValue: JSObject?) -> JSObject?
/**
* Get array parameter
* @param key Parameter name
* @returns JSArray or nil if not found
*/
public func getArray(_ key: String) -> JSArray?
/**
* Get array parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns JSArray or default
*/
public func getArray(_ key: String, _ defaultValue: JSArray?) -> JSArray?
/**
* Get date parameter
* @param key Parameter name
* @returns Date object or nil if not found/invalid
*/
public func getDate(_ key: String) -> Date?
/**
* Get date parameter with default value
* @param key Parameter name
* @param defaultValue Default value if parameter not found
* @returns Date object or default
*/
public func getDate(_ key: String, _ defaultValue: Date?) -> Date?
}Usage Examples:
class MyPlugin: CAPPlugin {
@objc func processData(_ call: CAPPluginCall) {
// Extract parameters with type safety
let message = call.getString("message", "Default message")
let count = call.getInt("count", 1)
let enabled = call.getBool("enabled", true)
let options = call.getObject("options") ?? [:]
let items = call.getArray("items") ?? []
// Validate required parameters
guard let userId = call.getString("userId") else {
call.reject("Missing required parameter: userId")
return
}
// Perform processing
processDataAsync(
message: message,
count: count,
userId: userId,
enabled: enabled,
options: options,
items: items
) { result in
switch result {
case .success(let data):
call.resolve(["result": data, "processed": true])
case .failure(let error):
call.reject("Processing failed", "PROCESS_ERROR", error)
}
}
}
@objc func uploadFile(_ call: CAPPluginCall) {
// Handle complex object parameters
guard let fileInfo = call.getObject("file") else {
call.reject("File information required")
return
}
let filename = fileInfo["name"] as? String ?? "untitled"
let size = fileInfo["size"] as? Int ?? 0
let mimeType = fileInfo["type"] as? String ?? "application/octet-stream"
// Upload with progress
uploadFile(name: filename, size: size, mimeType: mimeType) { progress in
// For progress updates, save the call and use notifyListeners
self.notifyListeners("uploadProgress", data: [
"callId": call.callbackId,
"progress": progress
])
} completion: { success, error in
if success {
call.resolve(["uploaded": true, "filename": filename])
} else {
call.reject("Upload failed", "UPLOAD_ERROR", error)
}
}
}
@objc func validateInput(_ call: CAPPluginCall) {
// Validate multiple parameters
let errors: [String] = []
guard call.getString("email") != nil else {
errors.append("Email is required")
}
let age = call.getInt("age", -1)
guard age >= 0 && age <= 120 else {
errors.append("Age must be between 0 and 120")
}
if !errors.isEmpty {
call.reject("Validation failed", "VALIDATION_ERROR", nil, [
"errors": errors
])
return
}
call.resolve(["valid": true])
}
}Type definitions and protocols for safe JavaScript-to-Swift data conversion.
/**
* Base protocol for all JavaScript-compatible value types
*/
public protocol JSValue {}
// Standard types conforming to JSValue
extension String: JSValue {}
extension Bool: JSValue {}
extension Int: JSValue {}
extension Float: JSValue {}
extension Double: JSValue {}
extension NSNumber: JSValue {}
extension NSString: JSValue {}
extension NSArray: JSValue {}
extension NSDictionary: JSValue {}
/**
* Type aliases for JavaScript data structures
*/
public typealias JSObject = [String: JSValue]
public typealias JSArray = [JSValue]
public typealias PluginCallResultData = JSObject
/**
* Protocol for containers that provide type-safe value access
*/
public protocol JSValueContainer {
/**
* Get value by key as specific type
* @param key Key to lookup
* @returns Value cast to specified type or nil
*/
func getValue<T>(_ key: String, as type: T.Type) -> T?
}
/**
* String value container protocol
*/
public protocol JSStringContainer {
func getString(_ key: String) -> String?
func getString(_ key: String, _ defaultValue: String) -> String
}
/**
* Boolean value container protocol
*/
public protocol JSBoolContainer {
func getBool(_ key: String) -> Bool
func getBool(_ key: String, _ defaultValue: Bool) -> Bool
}
/**
* Numeric value container protocols
*/
public protocol JSIntContainer {
func getInt(_ key: String) -> Int
func getInt(_ key: String, _ defaultValue: Int) -> Int
}
public protocol JSFloatContainer {
func getFloat(_ key: String) -> Float
func getFloat(_ key: String, _ defaultValue: Float) -> Float
}
public protocol JSDoubleContainer {
func getDouble(_ key: String) -> Double
func getDouble(_ key: String, _ defaultValue: Double) -> Double
}
/**
* Object and array container protocols
*/
public protocol JSObjectContainer {
func getObject(_ key: String) -> JSObject?
func getObject(_ key: String, _ defaultValue: JSObject?) -> JSObject?
}
public protocol JSArrayContainer {
func getArray(_ key: String) -> JSArray?
func getArray(_ key: String, _ defaultValue: JSArray?) -> JSArray?
}
/**
* Date container protocol
*/
public protocol JSDateContainer {
func getDate(_ key: String) -> Date?
func getDate(_ key: String, _ defaultValue: Date?) -> Date?
}Structured response objects for plugin method results and errors.
/**
* Successful plugin method response
*/
@objc(CAPPluginCallResult)
public class CAPPluginCallResult: NSObject {
/** Response data payload */
public let data: PluginCallResultData?
/**
* Initialize with response data
* @param data Response data dictionary
*/
public init(data: PluginCallResultData?)
}
/**
* Plugin method error response
*/
@objc(CAPPluginCallError)
public class CAPPluginCallError: NSObject {
/** Error description message */
public let message: String
/** Error code identifier */
public let code: String?
/** Underlying system error */
public let error: Error?
/** Additional error data */
public let data: PluginCallResultData?
/**
* Initialize with error details
* @param message Error message
* @param code Error code
* @param error Underlying error
* @param data Additional error data
*/
public init(message: String, code: String?, error: Error?, data: PluginCallResultData?)
}System for calling JavaScript functions from native code.
/**
* Protocol for executing JavaScript functions from native code
*/
@objc public protocol JSExport {
/**
* Call JavaScript function with no arguments
* @param method Function name to call
*/
func call(method: String)
/**
* Call JavaScript function with single argument
* @param method Function name to call
* @param argument Single argument value
*/
func call(method: String, _ argument: JSValue)
/**
* Call JavaScript function with multiple arguments
* @param method Function name to call
* @param arguments Array of argument values
*/
func call(method: String, _ arguments: [JSValue])
}JavaScript Export Usage:
class MyPlugin: CAPPlugin {
@objc func triggerJavaScript(_ call: CAPPluginCall) {
let functionName = call.getString("function", "defaultFunction")
let args = call.getArray("args") ?? []
// Call JavaScript function
if let jsExport = webView as? JSExport {
jsExport.call(method: functionName, args)
}
call.resolve()
}
@objc func notifyJavaScript(_ call: CAPPluginCall) {
let event = call.getString("event", "notification")
let data = call.getObject("data") ?? [:]
// Execute JavaScript directly
let jsCode = "window.dispatchEvent(new CustomEvent('\(event)', { detail: \(data) }));"
bridge?.eval(js: jsCode)
call.resolve()
}
}Patterns for handling complex data types and conversion scenarios.
class DataPlugin: CAPPlugin {
@objc func processComplexData(_ call: CAPPluginCall) {
// Handle nested objects
guard let userObj = call.getObject("user") else {
call.reject("User object required")
return
}
let userName = userObj["name"] as? String ?? "Unknown"
let userAge = userObj["age"] as? Int ?? 0
let userPrefs = userObj["preferences"] as? JSObject ?? [:]
// Handle arrays of objects
let items = call.getArray("items") ?? []
var processedItems: [JSObject] = []
for item in items {
if let itemObj = item as? JSObject {
let processedItem: JSObject = [
"id": itemObj["id"] ?? "",
"processed": true,
"timestamp": Date().timeIntervalSince1970
]
processedItems.append(processedItem)
}
}
// Return complex response
call.resolve([
"user": [
"name": userName,
"age": userAge,
"preferences": userPrefs
],
"processedItems": processedItems,
"totalProcessed": processedItems.count
])
}
@objc func handleDates(_ call: CAPPluginCall) {
// Handle date parameters
let startDate = call.getDate("startDate") ?? Date()
let endDate = call.getDate("endDate") ?? Date()
let formatter = DateFormatter()
formatter.dateStyle = .full
call.resolve([
"startDate": formatter.string(from: startDate),
"endDate": formatter.string(from: endDate),
"duration": endDate.timeIntervalSince(startDate)
])
}
}