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