1111//===----------------------------------------------------------------------===//
1212
1313import JavaScriptCore
14- import SwiftPluginServerSupport
1514
16- // returns: Promise<(ArrayBuffer) => ArrayBuffer>
15+ // returns: (wasm: ArrayBuffer) => Promise<(input: ArrayBuffer) => ArrayBuffer>
1716private let js = """
18- ( async () => {
17+ async (wasmData ) => {
1918 const mod = await WebAssembly.compile(wasmData);
2019 // stub WASI imports
2120 const imports = WebAssembly.Module.imports(mod)
@@ -38,39 +37,49 @@ private let js = """
3837 api.wacro_free(outAddr);
3938 return copy.buffer;
4039 }
41- })()
40+ }
4241"""
4342
4443@available ( macOS 10 . 15 , * )
4544final class JSCWasmPlugin : WasmPlugin {
45+ private static let factory = JSContext ( ) ? . evaluateScript ( js)
46+
4647 private let handler : JSValue
4748
4849 @MainActor init ( wasm data: Data ) async throws {
49- guard let context = JSContext ( ) else { throw PluginServerError ( message: " Could not create JSContext " ) }
50-
51- let jsBuf = try JSValue ( newBufferWithData: data, in: context)
52- context. globalObject. setObject ( jsBuf, forKeyedSubscript: " wasmData " )
53-
54- guard let promise = context. evaluateScript ( js) else {
55- throw PluginServerError ( message: " Failed to load plugin " )
50+ guard let factory = Self . factory, let context = factory. context else {
51+ throw JSCWasmError ( message: " Failed to load plugin " )
5652 }
5753
58- if let error = context. exception {
59- throw PluginServerError ( message: " Failed to load plugin: \( error) " )
54+ let jsBuf = try JSValue ( newBufferWithData: data, in: context)
55+ guard let promise = factory. call ( withArguments: [ jsBuf] ) , context. exception == nil else {
56+ throw JSCWasmError ( message: " Failed to load plugin " , value: context. exception)
6057 }
6158
6259 handler = try await promise. promiseValue
6360 }
6461
6562 @MainActor func handleMessage( _ json: Data ) throws -> Data {
66- let jsonJS = try JSValue ( newBufferWithData: json, in: handler. context)
63+ guard let context = handler. context else {
64+ throw JSCWasmError ( message: " Failed to invoke plugin " )
65+ }
66+ let jsonJS = try JSValue ( newBufferWithData: json, in: context)
6767 guard let result = handler. call ( withArguments: [ jsonJS] ) else {
68- throw PluginServerError ( message: " Wasm plugin did not provide a valid response " )
68+ throw JSCWasmError ( message: " Wasm plugin did not provide a valid response " , value : context . exception )
6969 }
7070 return result. arrayBufferData ( )
7171 }
7272}
7373
74+ public struct JSCWasmError : Error , CustomStringConvertible {
75+ public let value : JSValue ?
76+ public let description : String
77+ public init ( message: String , value: JSValue ? = nil ) {
78+ self . value = value
79+ self . description = " \( message) \( value. map { " : \( $0) " } ?? " " ) "
80+ }
81+ }
82+
7483extension JSValue {
7584 fileprivate convenience init ( newBufferWithData data: Data , in context: JSContext ) throws {
7685 let copy = UnsafeMutableBufferPointer< UInt8> . allocate( capacity: data. count)
@@ -105,18 +114,18 @@ extension JSValue {
105114 let rejectFunc = JSValue (
106115 object: { error in
107116 continuation. resume (
108- throwing: PluginServerError ( message: " \( error ) " )
117+ throwing: JSCWasmError ( message: " Promise rejected " , value : error )
109118 )
110119 } as @convention ( block) ( JSValue ) -> Void ,
111120 in: context
112121 )
113122 guard let successFunc, let rejectFunc else {
114- continuation. resume ( throwing: PluginServerError ( message: " Could not await promise " ) )
123+ continuation. resume ( throwing: JSCWasmError ( message: " Could not await promise " ) )
115124 return
116125 }
117126 invokeMethod ( " then " , withArguments: [ successFunc, rejectFunc] )
118127 if let exception = context. exception {
119- continuation. resume ( throwing: PluginServerError ( message: " \( exception ) " ) )
128+ continuation. resume ( throwing: JSCWasmError ( message: " Promise.then threw " , value : exception ) )
120129 }
121130 }
122131 }
0 commit comments