// Created by Helge Heß 2021-06-17 import Foundation // They use obfuscated names to hide it from us! import JavaScriptCore /// Setup our async/await runtime. let runtime = JSContext()! /// Ask our async/await runtime to tell us about errors. runtime.exceptionHandler = { _, error in print("Caught exception:", error as Any) } /// Add a global `print` function to our async/await runtime runtime.setObject( {()->@convention(block) (JSValue)->Void in { print($0) }}(), forKeyedSubscript: "print" as NSString ) /// We need this protoocol to tell our async/await runtime about URLSession. @objc protocol AsyncURLSession: JSExport { /// Give access to `URLSession.shared` @objc(shared) // overlap w/ `sharedSession` on the ObjC side static var sharedSwift : URLSession { get } /// Our async/await enabled URL fetcher, /// returns an async error or a [ data, response ] tuple. func data(_ url: String) -> JSValue } /// Here we implement our async aware function. @objc extension URLSession: AsyncURLSession { dynamic class var sharedSwift : URLSession { shared } func data(_ url: String) -> JSValue { guard let ctx = JSContext.current() else { fatalError("No runtime") } /// Create our continuation return JSValue(newPromiseIn: ctx) { resolve, reject in guard let url = URL(string: url) else { reject?.call(withArguments: [ "invalidURL" ]); return } self.dataTask(with: URLRequest(url: url)) { data, response, error in RunLoop.main.perform { if let error = error { reject?.call(withArguments: [ error.localizedDescription ]) } else if let data = data, let response = response { resolve?.call(withArguments: [ [ data, response ] ]) } else { reject?.call(withArguments: [ "missingResponse" ]) } } } .resume() } } } runtime.setObject(URLSession.self, forKeyedSubscript: "URLSession" as NSString) runtime.evaluateScript("URLSession.shared = URLSession.shared();") /// Finally, execute our fetch using async/await. runtime.evaluateScript(#""" async function mainActor() { let [ data, res ] = await URLSession.shared.data("https://zeezide.com/#dontTrackMe") print(data) print(res) } mainActor() """#) /// Keep it alive RunLoop.main.run()