/* * This file is part of GNU Taler * (C) 2021 Taler Systems S.A. * * GNU Taler is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 3, or (at your option) any later version. * * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * GNU Taler; see the file COPYING. If not, see */ import Foundation public protocol IonoMessageHandler: AnyObject { func handleMessage(message: String) } func notification_callback(payload: Optional>, userdata: Optional) { let native = Unmanaged.fromOpaque(userdata!).takeUnretainedValue() let string = String(cString: payload!) native.internalOnNotify(payload: string) } struct Queue { var contents: [T] init() { self.contents = [] } mutating func push(_ element: T) { contents.append(element) } mutating func pop() -> T? { if (contents.isEmpty) { return nil } else { return contents.remove(at: 0) } } } class NodeThread: Thread { weak var iono: Iono! var workQueue: Queue<() -> ()> var initialized: Bool var initCondition: NSCondition override init() { self.workQueue = Queue<() -> ()>() self.initialized = false self.initCondition = NSCondition() super.init() } override func main() { iono.instance = __initNative() __setNotifyHandler(iono.instance, notification_callback, Unmanaged.passUnretained(iono).toOpaque()) self.initialized = true initCondition.broadcast() while true { __runNode(iono.instance) while let workItem = workQueue.pop() { workItem() } if iono.stopped { break } } } func waitUntilInitialized(block: @escaping () -> ()) { if (self.initialized) { block() return } initCondition.lock() while (!self.initialized) { initCondition.wait() } block() initCondition.unlock() } } public class Iono { var stopped: Bool var thread: NodeThread var instance: OpaquePointer! public weak var messageHandler: IonoMessageHandler? public init() { self.stopped = false self.thread = NodeThread() self.thread.iono = self self.thread.start() } deinit { __destroyNative(instance) } private func scheduleNodeThreadAsync(block: @escaping () -> ()) { thread.waitUntilInitialized { self.thread.workQueue.push(block) self.notifyNative() } } private func scheduleNodeThreadSync(block: @escaping () -> Void) { var hasExecuted = false let executeCondition = NSCondition() executeCondition.lock() thread.waitUntilInitialized { self.thread.workQueue.push { block() hasExecuted = true executeCondition.broadcast() } self.notifyNative() } while (!hasExecuted) { executeCondition.wait() } executeCondition.unlock() } public func internalOnNotify(payload: String) { if let handler = messageHandler { handler.handleMessage(message: payload) } } public func notifyNative() { __notifyNative(instance) } public func evalSimpleJs(source: String) -> String { var result: String? scheduleNodeThreadSync { let cResult = __evalJs(self.instance, source.cString(using: .utf8)) if let cStr = cResult { result = String(cString: cStr) free(cResult) } } return result! } public func evalNodeCode(source: String) { scheduleNodeThreadAsync { __makeCallbackNative(self.instance, source.cString(using: .utf8)) } } public func sendMessage(message: String) { let encoded = message.data(using: .utf8)!.base64EncodedString() let source = """ if (global.__native_onMessage) { const msg = (new Buffer.from('\(encoded)', 'base64')).toString('ascii'); global.__native_onMessage(msg); } else { console.log("WARN: no __native_onMessage defined"); } """ evalNodeCode(source: source) } /// Note: This *must* be called before releasing the object, or else the thread will keep going. public func waitStopped() { scheduleNodeThreadSync { self.stopped = true } thread.cancel() } public func putModuleCode(modName: String, code: String) { __putModuleCodeNative(self.instance, modName.cString(using: .utf8), code.cString(using: .utf8)) } }