taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

quickjs.swift (5755B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  * @author codex gpt5.6-sol
      8  */
      9 import Foundation
     10 import os.log
     11 
     12 import FTalerWalletcore
     13 
     14 public protocol QuickjsMessageHandler: AnyObject {
     15     func handleMessage(message: String)
     16     func handleLog(message: String)
     17 }
     18 // MARK: -
     19 func notification_callback(userdata: Optional<UnsafeMutableRawPointer>,
     20                            payload: Optional<UnsafePointer<Int8>>) {
     21     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     22     let string = String(cString: payload!)
     23     quickjs.internalOnNotify(payload: string)
     24 }
     25 
     26 func logging_callback(userdata: Optional<UnsafeMutableRawPointer>,
     27                           level: TALER_WALLET_LogLevel,
     28                             tag: Optional<UnsafePointer<Int8>>,
     29                         message: Optional<UnsafePointer<Int8>>) {
     30     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     31     let logger = quickjs.logger
     32     let swiftTag = String(cString: tag!)
     33     let swiftMessage = String(cString: message!)
     34 
     35     switch level {
     36         case TALER_WALLET_LOG_ERROR:
     37             logger.error("\(swiftTag, privacy: .public)  \(swiftMessage, privacy: .public)")
     38         case TALER_WALLET_LOG_WARN:
     39             logger.warning("\(swiftTag, privacy: .public)  \(swiftMessage, privacy: .public)")
     40         case TALER_WALLET_LOG_MESSAGE:
     41             logger.notice("\(swiftTag, privacy: .public)  \(swiftMessage, privacy: .public)")
     42         case TALER_WALLET_LOG_INFO:
     43             logger.info("\(swiftTag, privacy: .public)  \(swiftMessage, privacy: .public)")
     44         case TALER_WALLET_LOG_TRACE:
     45             logger.trace("\(swiftTag, privacy: .public)  \(swiftMessage, privacy: .public)")
     46         default: break
     47     }
     48     quickjs.internalOnLog(message: swiftMessage)
     49 }
     50 // MARK: -
     51 public class Quickjs {      // acts as singleton, since only one instance ever exists
     52     var talerWalletInstance: OpaquePointer!
     53     public weak var messageHandler: QuickjsMessageHandler?
     54     var logger: Logger
     55 
     56     @Atomic(default: 0)                                                         // boilerPlate around NSLock…
     57     private var lastRequestID: Int32                                            // …to safeguard increments
     58     private let requestsLock = NSLock()
     59     private var requests: [Int32: QuickDataTask] = [:]
     60 
     61     private lazy var urlSession: URLSession = {
     62         return URLSession(configuration: .ephemeral)    // we don't want the filesystem cache - keep it in RAM
     63     }()
     64 
     65     public init() {
     66         self.logger = Logger(subsystem: "net.taler.gnu", category: "QuickJS")
     67         self.talerWalletInstance = TALER_WALLET_create()
     68         TALER_WALLET_set_message_handler(talerWalletInstance,
     69                                          notification_callback,
     70                                          Unmanaged.passUnretained(self).toOpaque())
     71         TALER_WALLET_set_log_handler(talerWalletInstance,
     72                                      logging_callback,
     73                                      Unmanaged.passUnretained(self).toOpaque())
     74         let http_impl = TALER_pack_http_client_implementation(request_create, request_cancel,
     75                                                               Unmanaged.passUnretained(self).toOpaque())
     76         // http_impl got malloc'd, and could possibly be free'd when the app terminates
     77         TALER_set_http_client_implementation(talerWalletInstance, http_impl)
     78         // - but we never free anything on termination, thus we don't save http_impl here
     79         TALER_WALLET_run(talerWalletInstance)
     80     }
     81 
     82     func reqCreate(_ request: URLRequest,
     83                 _ responseCb: JSHttpResponseCb?,
     84              _ responseCbCls: Optional<UnsafeMutableRawPointer>) -> Int32 {
     85         let requestID = $lastRequestID.atomicIncrement()                        // ensure requestID is unique
     86         let quickDataTask = QuickDataTask(urlSession: urlSession,
     87                                              request: request,
     88                                            requestID: requestID,
     89                                           responseCb: responseCb,
     90                                        responseCbCls: responseCbCls) { [weak self] requestID in
     91             self?.removeRequest(requestID)
     92         }
     93 
     94         requestsLock.lock()
     95         requests[requestID] = quickDataTask
     96         requestsLock.unlock()
     97 
     98         quickDataTask.run()
     99         return requestID
    100     }
    101 
    102     func reqCancel(_ requestID: Int32) -> Int32 {
    103         requestsLock.lock()
    104         let quickDataTask = requests[requestID]
    105         requestsLock.unlock()
    106 
    107         quickDataTask?.cancel()
    108         return 0
    109     }
    110 
    111     private func removeRequest(_ requestID: Int32) {
    112         requestsLock.lock()
    113         requests[requestID] = nil
    114         requestsLock.unlock()
    115     }
    116 
    117     deinit {
    118         // No need to call TALER_WALLET_destroy - memory gets purged anyway
    119     }
    120 
    121     func internalOnNotify(payload: String) {
    122         guard let handler = messageHandler else { return }
    123         DispatchQueue.main.async {
    124             handler.handleMessage(message: payload)
    125         }
    126     }
    127 
    128     public func internalOnLog(message: String) {
    129         guard let handler = messageHandler else { return }
    130         DispatchQueue.main.async {
    131             handler.handleLog(message: message)
    132         }
    133     }
    134 
    135 //    public func notifyNative() {
    136 //        __notifyNative(instance)
    137 //    }
    138 
    139 //    public func evalNodeCode(source: String) {
    140 //        scheduleNodeThreadAsync {
    141 //            __makeCallbackNative(self.instance, source.cString(using: .utf8))
    142 //        }
    143 //    }
    144 
    145     public func sendMessage(message: String) {
    146         TALER_WALLET_send_request(talerWalletInstance, message)
    147     }
    148 }