iota-sdk 0.3.0

The IOTA SDK provides developers with a seamless experience to develop on IOTA by providing account abstractions and clients to interact with node APIs.
Documentation
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import Foundation
import Capacitor
import Wallet

@objc(IotaWalletMobile)
public class IotaWalletMobile: CAPPlugin {
    
    // Handles the Swift / C pointers with a context object using Unmanaged types
    class ContextResult {
        // the `call` will send the context response back to Javascript
        let call: CAPPluginCall?
        
        init(_call: CAPPluginCall) {
            self.call = _call
        }
        
        func resolve(type: String, data: String) {
            self.call?.resolve([type: data])
        }
        // the needed `callback` to call C header functions
        let callback: Callback = { response, error, context  in
            guard let context = context,
                  let response = response else { return }
            // Convert back into `ContextResult` Swift object type
            let contextResult = Unmanaged<ContextResult>.fromOpaque(context).takeRetainedValue()
            if let error = error {
                contextResult.resolve(type: "error", data: String(cString: error))
                return
            }
            contextResult.resolve(type: "result", data: String(cString: response))
        }
        
        let callbackListen: Callback = { response, error, context  in
            guard let context = context,
                  let response = response else { return }
            // retain of the object awaiting for the next message.
            let contextResult = Unmanaged<ContextResult>.fromOpaque(context).retain().takeRetainedValue()
            
            if let error = error {
                contextResult.resolve(type: "error", data: String(cString: error))
                return
            }
            contextResult.resolve(type: "result", data: String(cString: response))
        }
    }

    @objc func messageHandlerNew(_ call: CAPPluginCall) {
        do {
            guard let storagePath = call.getString("storagePath"),
                  let clientOptions = call.getObject("clientOptions"),
                  let coinType = call.getInt("coinType"),
                  let secretManager = call.getObject("secretManager") else {
                return call.reject("storagePath, clientOptions, coinType, and secretManager are required")
            }
            guard JSONSerialization.isValidJSONObject(clientOptions),
                  JSONSerialization.isValidJSONObject(secretManager) else {
                return call.reject("clientOptions or secretManager is an invalid JSON object")
            }
            let ClientOptions = try? JSONSerialization.data(withJSONObject: clientOptions)
            let stringfiedClientOptions = String(data: ClientOptions!, encoding: .utf8)!.replacingOccurrences(of: "\\", with: "")
            
            // prepare the internal app directory path
            let fm = FileManager.default
            guard let documents = fm.urls(
                for: .applicationSupportDirectory,
                   in: .userDomainMask
            ).first else { 
                return  call.reject("can not create the path")
            }
            let path = documents.appendingPathComponent(
                storagePath,
                isDirectory: true
            ).path
            
            if !fm.fileExists(atPath: path) {
                try fm.createDirectory(
                    atPath: path,
                    withIntermediateDirectories: true,
                    attributes: nil
                )
            }
            
            // Exclude folder from auto-backup
            var urlPath = URL(fileURLWithPath: path, isDirectory: true)
            var values = URLResourceValues()
            values.isExcludedFromBackup = true
            try urlPath.setResourceValues(values)

            // we need to modify the path on the JS object
            let options = """
            {
                "storagePath":"\(path)",
                "clientOptions":\(String(describing: stringfiedClientOptions)),
                "coinType":\(coinType),
                "secretManager":{
                    "stronghold":{
                        "snapshotPath":"\(path)/wallet.stronghold"
                }}
            }
            """
            
            let error_buffer: UnsafeMutablePointer<CChar>? = nil
            let error_buffer_size = 0
        
            // Keep the C++ handler / pointer of the messageHandler call result
            let handler: OpaquePointer? = iota_initialize(options.cString(using: .utf8), error_buffer, error_buffer_size)
            // Convert pointer to integer keeping bit pattern
            call.resolve(["messageHandler": Int(bitPattern: handler)])
        
        } catch {
            call.reject("failed to initialize messageHandlerNew")
        }
    }

    @objc func destroy(_ call: CAPPluginCall) {
        guard let handler = call.getInt("messageHandler") else {
            return call.reject("handler is required")
        }
        iota_destroy(OpaquePointer(bitPattern: handler))
        call.resolve()
    }

    @objc func sendMessage(_ call: CAPPluginCall) {
        guard let handler = call.getInt("handler") else {
            return call.reject("handler is required")
        }
        let messageHandler: OpaquePointer? = OpaquePointer(bitPattern: handler)
        
        // replacing for urls slashes since it's serialized on JS
        guard let message = call.getString("message")?
                .replacingOccurrences(of: "\\", with: "") else {
            return call.reject("message is required")
        }
        
        // the object to be passed as a context data on their callback
        let contextResult = ContextResult(_call: call)
        // context var to be passed on the object callback
        // where it will be converted back to object type ready to use
        let context = Unmanaged<ContextResult>.passRetained(contextResult).toOpaque()
        
        iota_send_message(messageHandler, message.cString(using: .utf8), contextResult.callback, context)
    }

    @objc func listen(_ call: CAPPluginCall) {
        guard let handler = call.getInt("messageHandler") else {
            return call.reject("handler is required")
        }
        let messageHandler: OpaquePointer? = OpaquePointer(bitPattern: handler)
        guard let eventTypes = call.getArray("eventTypes") else {
            return call.reject("eventTypes is required")
        }
        
        let contextResult = ContextResult(_call: call)
        let context = Unmanaged<ContextResult>.passRetained(contextResult).toOpaque()
        
        let error_buffer: UnsafeMutablePointer<CChar>? = nil
        let error_buffer_size = 0
        
        iota_listen(
            messageHandler, eventTypes.description,
            contextResult.callbackListen, context,
            error_buffer, error_buffer_size
        )
        
        call.keepAlive = true
    }

}