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 }